mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-15 14:52:26 +00:00
Linux: the title bar carries the window controls, as in Excel (#192)
This commit is contained in:
1 parent
40783ab184
commit
494b830240
25 files changed
+259
-2
No files matched your search
@@ -340,12 +340,16 @@ fn main() -> eframe::Result<()> {
|
||||
for f in &files {
|
||||
app.open_path(f);
|
||||
}
|
||||
// Linux/BSD: Excel's layout — no window-manager title bar above ours; our title bar carries the
|
||||
// window controls. `GRIDCRAFT_SYSTEM_TITLEBAR=1` keeps the system frame.
|
||||
app.window_controls = cfg!(not(any(target_os = "macos", target_os = "windows"))) && std::env::var_os("GRIDCRAFT_SYSTEM_TITLEBAR").is_none();
|
||||
let mut viewport = egui::ViewportBuilder::default()
|
||||
.with_title("GridCraft")
|
||||
.with_inner_size([1440.0, 900.0])
|
||||
.with_min_inner_size([640.0, 420.0])
|
||||
.with_app_id("ai.storyteller.gridcraft")
|
||||
.with_drag_and_drop(true);
|
||||
.with_drag_and_drop(true)
|
||||
.with_decorations(!app.window_controls);
|
||||
if cfg!(target_os = "macos") {
|
||||
viewport = viewport.with_fullsize_content_view(true).with_titlebar_shown(false).with_title_shown(false);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ impl SheetApp {
|
||||
macros: macro_ui::MacroUi::default(),
|
||||
started: now_ms(),
|
||||
allow_close: false,
|
||||
window_controls: false,
|
||||
viewport_commands: Vec::new(),
|
||||
bring_forward: false,
|
||||
open_switch_windows: false,
|
||||
ui_window: None,
|
||||
@@ -252,6 +254,9 @@ impl SheetApp {
|
||||
macro_ui::question_box(self, &ctx);
|
||||
widgets::message_box(self, &ctx);
|
||||
widgets::toast(self, &ctx);
|
||||
// Last, so its resize cursors win over the widgets under the window edges.
|
||||
ribbon::window_frame(self, &ctx);
|
||||
self.flush_viewport_commands(&ctx);
|
||||
// Requests are admitted in `logic`, for the root viewport, so only its pass issues them.
|
||||
if ctx.viewport_id() == egui::ViewportId::ROOT {
|
||||
control::issue_screenshots(self, &ctx);
|
||||
@@ -304,6 +309,12 @@ impl SheetApp {
|
||||
}
|
||||
sel.active.a1()
|
||||
}
|
||||
|
||||
pub(crate) fn flush_viewport_commands(&mut self, ctx: &egui::Context) {
|
||||
for command in self.viewport_commands.drain(..) {
|
||||
ctx.send_viewport_cmd(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -163,6 +163,26 @@ impl SheetApp {
|
||||
/// UI-only commands (dialogs, view toggles that aren't saved in the file).
|
||||
fn run_ui_command(&mut self, id: &str, p: &Json) -> Option<Result<Json, String>> {
|
||||
let r = match id {
|
||||
"ui.window.minimize" => {
|
||||
self.viewport_commands.push(egui::ViewportCommand::Minimized(true));
|
||||
Ok(Json::Null)
|
||||
}
|
||||
"ui.window.maximize" => {
|
||||
let on = p.get("on").and_then(Json::as_bool).unwrap_or(true);
|
||||
self.viewport_commands.push(egui::ViewportCommand::Maximized(on));
|
||||
Ok(Json::Null)
|
||||
}
|
||||
"ui.window.close" => {
|
||||
self.viewport_commands.push(egui::ViewportCommand::Close);
|
||||
Ok(Json::Null)
|
||||
}
|
||||
"ui.window.beginResize" => {
|
||||
let Some(direction) = p.get("direction").and_then(Json::as_str).and_then(crate::parse_resize_direction) else {
|
||||
return Some(Err("direction must be north, south, east, west, northeast, northwest, southeast, or southwest".into()));
|
||||
};
|
||||
self.viewport_commands.push(egui::ViewportCommand::BeginResize(direction));
|
||||
Ok(Json::Null)
|
||||
}
|
||||
"app.checkForUpdates" | "help.checkForUpdates" => {
|
||||
self.check_for_updates();
|
||||
Ok(Json::Null)
|
||||
|
||||
@@ -275,6 +275,10 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) -
|
||||
("options.autoComplete", "Enable AutoComplete for cell values", "{on?: bool} (toggles without it) → {on}"),
|
||||
("options.highlightActiveRow", "Highlight the active cell's row", "{on?: bool} (toggles without it) → {on}"),
|
||||
("view.interfaceScale", "Interface Size", "{percent: 75–200 | \"auto\"} (100 = auto; clamped)"),
|
||||
("ui.window.minimize", "Minimize Window", ""),
|
||||
("ui.window.maximize", "Maximize Window", "{on: bool}"),
|
||||
("ui.window.close", "Close Window", ""),
|
||||
("ui.window.beginResize", "Resize Window", "{direction: north|south|east|west|northeast|northwest|southeast|southwest}"),
|
||||
("ui.dialog", "Open Dialog", ""),
|
||||
("ui.backstage", "File (Backstage view)", "{page?: home|new|open|info|saveAs|print|export|options|account|close}"),
|
||||
(
|
||||
|
||||
@@ -278,6 +278,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Uložit",
|
||||
"Save As" => "Uložit jako",
|
||||
"Export" => "Exportovat",
|
||||
"Minimize" => "Minimalizovat",
|
||||
"Maximize" => "Maximalizovat",
|
||||
"Restore Down" => "Obnovit dolů",
|
||||
"Close" => "Zavřít",
|
||||
"Options" => "Možnosti",
|
||||
"Account" => "Účet",
|
||||
|
||||
@@ -294,6 +294,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Speichern",
|
||||
"Save As" => "Speichern unter",
|
||||
"Export" => "Exportieren",
|
||||
"Minimize" => "Minimieren",
|
||||
"Maximize" => "Maximieren",
|
||||
"Restore Down" => "Nach unten wiederherstellen",
|
||||
"Close" => "Schließen",
|
||||
"Options" => "Optionen",
|
||||
"Account" => "Konto",
|
||||
|
||||
@@ -271,6 +271,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Αποθήκευση",
|
||||
"Save As" => "Αποθήκευση ως",
|
||||
"Export" => "Εξαγωγή",
|
||||
"Minimize" => "Ελαχιστοποίηση",
|
||||
"Maximize" => "Μεγιστοποίηση",
|
||||
"Restore Down" => "Επαναφορά",
|
||||
"Close" => "Κλείσιμο",
|
||||
"Options" => "Επιλογές",
|
||||
"Blank workbook" => "Κενό βιβλίο εργασίας",
|
||||
|
||||
@@ -283,6 +283,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Guardar",
|
||||
"Save As" => "Guardar como",
|
||||
"Export" => "Exportar",
|
||||
"Minimize" => "Minimizar",
|
||||
"Maximize" => "Maximizar",
|
||||
"Restore Down" => "Restaurar",
|
||||
"Close" => "Cerrar",
|
||||
"Options" => "Opciones",
|
||||
"Account" => "Cuenta",
|
||||
|
||||
@@ -370,6 +370,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Enregistrer",
|
||||
"Save As" => "Enregistrer sous",
|
||||
"Export" => "Exporter",
|
||||
"Minimize" => "Réduire",
|
||||
"Maximize" => "Agrandir",
|
||||
"Restore Down" => "Restaurer",
|
||||
"Close" => "Fermer",
|
||||
"Options" => "Options",
|
||||
"Account" => "Compte",
|
||||
|
||||
@@ -365,6 +365,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Salva",
|
||||
"Save As" => "Salva con nome",
|
||||
"Export" => "Esporta",
|
||||
"Minimize" => "Riduci a icona",
|
||||
"Maximize" => "Ingrandisci",
|
||||
"Restore Down" => "Ripristina",
|
||||
"Close" => "Chiudi",
|
||||
"Options" => "Opzioni",
|
||||
"Blank workbook" => "Cartella di lavoro vuota",
|
||||
|
||||
@@ -364,6 +364,9 @@ pub(super) const CORE: &[(&str, &str)] = &[
|
||||
("Save", "上書き保存"),
|
||||
("Save As", "名前を付けて保存"),
|
||||
("Export", "エクスポート"),
|
||||
("Minimize", "最小化"),
|
||||
("Maximize", "最大化"),
|
||||
("Restore Down", "元のサイズに戻す"),
|
||||
("Close", "閉じる"),
|
||||
("Options", "オプション"),
|
||||
("Blank workbook", "空白のブック"),
|
||||
|
||||
@@ -370,6 +370,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "저장",
|
||||
"Save As" => "다른 이름으로 저장",
|
||||
"Export" => "내보내기",
|
||||
"Minimize" => "최소화",
|
||||
"Maximize" => "최대화",
|
||||
"Restore Down" => "이전 크기로 복원",
|
||||
"Close" => "닫기",
|
||||
"Options" => "옵션",
|
||||
"Account" => "계정",
|
||||
|
||||
@@ -364,6 +364,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Opslaan",
|
||||
"Save As" => "Opslaan als",
|
||||
"Export" => "Exporteren",
|
||||
"Minimize" => "Minimaliseren",
|
||||
"Maximize" => "Maximaliseren",
|
||||
"Restore Down" => "Vorig formaat",
|
||||
"Close" => "Sluiten",
|
||||
"Options" => "Opties",
|
||||
"Blank workbook" => "Lege werkmap",
|
||||
|
||||
@@ -357,6 +357,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Zapisz",
|
||||
"Save As" => "Zapisz jako",
|
||||
"Export" => "Eksportuj",
|
||||
"Minimize" => "Minimalizuj",
|
||||
"Maximize" => "Maksymalizuj",
|
||||
"Restore Down" => "Przywróć w dół",
|
||||
"Close" => "Zamknij",
|
||||
"Options" => "Opcje",
|
||||
"Blank workbook" => "Pusty skoroszyt",
|
||||
|
||||
@@ -367,6 +367,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Salvar",
|
||||
"Save As" => "Salvar como",
|
||||
"Export" => "Exportar",
|
||||
"Minimize" => "Minimizar",
|
||||
"Maximize" => "Maximizar",
|
||||
"Restore Down" => "Restaurar",
|
||||
"Close" => "Fechar",
|
||||
"Options" => "Opções",
|
||||
"Account" => "Conta",
|
||||
|
||||
@@ -364,6 +364,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Salvare",
|
||||
"Save As" => "Salvare ca",
|
||||
"Export" => "Exportare",
|
||||
"Minimize" => "Minimizare",
|
||||
"Maximize" => "Maximizare",
|
||||
"Restore Down" => "Restaurare",
|
||||
"Close" => "Închidere",
|
||||
"Options" => "Opțiuni",
|
||||
"Blank workbook" => "Registru gol",
|
||||
|
||||
@@ -372,6 +372,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Сохранить",
|
||||
"Save As" => "Сохранить как",
|
||||
"Export" => "Экспорт",
|
||||
"Minimize" => "Свернуть",
|
||||
"Maximize" => "Развернуть",
|
||||
"Restore Down" => "Восстановить",
|
||||
"Close" => "Закрыть",
|
||||
"Options" => "Параметры",
|
||||
"Account" => "Учетная запись",
|
||||
|
||||
@@ -363,6 +363,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Sačuvaj",
|
||||
"Save As" => "Sačuvaj kao",
|
||||
"Export" => "Izvezi",
|
||||
"Minimize" => "Minimizuj",
|
||||
"Maximize" => "Maksimizuj",
|
||||
"Restore Down" => "Vrati",
|
||||
"Close" => "Zatvori",
|
||||
"Options" => "Opcije",
|
||||
"Account" => "Nalog",
|
||||
|
||||
@@ -277,6 +277,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Kaydet",
|
||||
"Save As" => "Farklı Kaydet",
|
||||
"Export" => "Dışarı Aktar",
|
||||
"Minimize" => "Simge durumuna küçült",
|
||||
"Maximize" => "Ekranı kapla",
|
||||
"Restore Down" => "Önceki boyuta getir",
|
||||
"Close" => "Kapat",
|
||||
"Options" => "Seçenekler",
|
||||
"Blank workbook" => "Boş çalışma kitabı",
|
||||
|
||||
@@ -361,6 +361,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Зберегти",
|
||||
"Save As" => "Зберегти як",
|
||||
"Export" => "Експорт",
|
||||
"Minimize" => "Згорнути",
|
||||
"Maximize" => "Розгорнути",
|
||||
"Restore Down" => "Відновити",
|
||||
"Close" => "Закрити",
|
||||
"Options" => "Параметри",
|
||||
"Blank workbook" => "Порожня книга",
|
||||
|
||||
@@ -367,6 +367,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "Lưu",
|
||||
"Save As" => "Lưu thành",
|
||||
"Export" => "Xuất",
|
||||
"Minimize" => "Thu nhỏ",
|
||||
"Maximize" => "Phóng to",
|
||||
"Restore Down" => "Khôi phục",
|
||||
"Close" => "Đóng",
|
||||
"Options" => "Tùy chọn",
|
||||
"Blank workbook" => "Sổ làm việc trống",
|
||||
|
||||
@@ -368,6 +368,9 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Save" => "保存",
|
||||
"Save As" => "另存为",
|
||||
"Export" => "导出",
|
||||
"Minimize" => "最小化",
|
||||
"Maximize" => "最大化",
|
||||
"Restore Down" => "向下还原",
|
||||
"Close" => "关闭",
|
||||
"Options" => "选项",
|
||||
"Account" => "账户",
|
||||
|
||||
@@ -83,6 +83,20 @@ pub use services::{Chrome, Inbox, Services};
|
||||
pub use state::{NoticeKind, OpenWindow, SaveAnswer, SheetView};
|
||||
pub mod desktop_theme;
|
||||
|
||||
fn parse_resize_direction(value: &str) -> Option<egui::ResizeDirection> {
|
||||
match value {
|
||||
"north" => Some(egui::ResizeDirection::North),
|
||||
"south" => Some(egui::ResizeDirection::South),
|
||||
"east" => Some(egui::ResizeDirection::East),
|
||||
"west" => Some(egui::ResizeDirection::West),
|
||||
"northeast" => Some(egui::ResizeDirection::NorthEast),
|
||||
"northwest" => Some(egui::ResizeDirection::NorthWest),
|
||||
"southeast" => Some(egui::ResizeDirection::SouthEast),
|
||||
"southwest" => Some(egui::ResizeDirection::SouthWest),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Persisted UI preferences.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
@@ -308,6 +322,10 @@ pub struct SheetApp {
|
||||
/// Every unsaved workbook has been answered for quitting (or `app.quit` asked): the next
|
||||
/// window close goes through.
|
||||
pub allow_close: bool,
|
||||
/// Linux/BSD: the window has no system frame; the title bar draws minimize, maximize and
|
||||
/// close, and the window resizes from its edges (`ribbon::window_frame`).
|
||||
pub window_controls: bool,
|
||||
viewport_commands: Vec<egui::ViewportCommand>,
|
||||
/// A quit question is waiting in the active window: [`doc_windows`] brings its native window
|
||||
/// to the front, also when the app is hidden or the window minimized (a Dock quit).
|
||||
pub bring_forward: bool,
|
||||
@@ -341,3 +359,32 @@ pub fn clean_error(e: &str) -> String {
|
||||
pub fn cell(r: u32, c: u32) -> CellRef {
|
||||
CellRef::new(r, c)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod viewport_command_tests {
|
||||
use super::parse_resize_direction;
|
||||
|
||||
#[test]
|
||||
fn resize_direction_parser_accepts_all_edges_and_rejects_unknown_values() {
|
||||
for name in ["north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest"] {
|
||||
assert!(parse_resize_direction(name).is_some(), "{name}");
|
||||
}
|
||||
assert!(parse_resize_direction("middle").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_commands_queue_viewport_commands_without_a_workbook() {
|
||||
use egui::ViewportCommand;
|
||||
let mut a = crate::SheetApp::new(gridcraft_engine::Session::new(), crate::Services::default());
|
||||
for (id, params, expected) in [
|
||||
("ui.window.minimize", serde_json::json!({}), ViewportCommand::Minimized(true)),
|
||||
("ui.window.maximize", serde_json::json!({"on": true}), ViewportCommand::Maximized(true)),
|
||||
("ui.window.close", serde_json::json!({}), ViewportCommand::Close),
|
||||
("ui.window.beginResize", serde_json::json!({"direction": "east"}), ViewportCommand::BeginResize(egui::ResizeDirection::East)),
|
||||
] {
|
||||
assert!(a.run(id, params).is_ok(), "{id} should route without a workbook");
|
||||
assert!(a.viewport_commands.contains(&expected), "{id} queues {expected:?}");
|
||||
}
|
||||
assert!(a.run("ui.window.beginResize", serde_json::json!({"direction": "diagonal"})).is_err());
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ use insert::{draw, insert};
|
||||
use page_layout::page_layout;
|
||||
use review::review;
|
||||
pub use shortcuts::shortcut;
|
||||
pub use title_bar::{MAC_CONTENT_LEFT, MAC_TITLE_BAR, TITLE_LEFT_PAD, title_bar};
|
||||
pub use title_bar::{MAC_CONTENT_LEFT, MAC_TITLE_BAR, TITLE_LEFT_PAD, title_bar, window_frame};
|
||||
use title_bar::{TITLE_BAR_H, TITLE_CONTROL_H};
|
||||
use view::view;
|
||||
|
||||
|
||||
@@ -120,6 +120,9 @@ pub fn title_bar(app: &mut SheetApp, ui: &mut Ui) {
|
||||
});
|
||||
let left_end = ui.min_rect().right();
|
||||
let right = ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if app.window_controls {
|
||||
ui.add_space(3.0 * CONTROL_W);
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
let __k = icon_button(ui, Icon::Search, t.text_dim, "Search commands", vec2(28.0, ch));
|
||||
control_keytip(app, ui, &__k, "commandSearch");
|
||||
@@ -206,6 +209,120 @@ fn paint_title(ui: &Ui, center: egui::Pos2, title: &str, dirty: bool, max_w: f32
|
||||
}
|
||||
}
|
||||
|
||||
/// Width of one window-control button on the title bar.
|
||||
pub const CONTROL_W: f32 = 46.0;
|
||||
const CONTROL_TITLE_H: f32 = 38.0;
|
||||
const RESIZE_BAND: f32 = 5.0;
|
||||
|
||||
/// Draw application controls and edge resizing for frameless windows.
|
||||
pub fn window_frame(app: &mut SheetApp, ctx: &egui::Context) {
|
||||
if !app.window_controls {
|
||||
return;
|
||||
}
|
||||
let (maximized, fullscreen) = ctx.input(|i| (i.viewport().maximized.unwrap_or(false), i.viewport().fullscreen.unwrap_or(false)));
|
||||
if fullscreen {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get(ctx);
|
||||
let screen = ctx.content_rect();
|
||||
let controls = egui::Rect::from_min_size(egui::pos2(screen.max.x - 3.0 * CONTROL_W, screen.min.y), egui::vec2(3.0 * CONTROL_W, CONTROL_TITLE_H));
|
||||
egui::Area::new(egui::Id::unique("gc_window_controls")).order(egui::Order::Foreground).fixed_pos(controls.min).show(ctx, |ui| {
|
||||
ui.spacing_mut().item_spacing = egui::vec2(0.0, 0.0);
|
||||
ui.horizontal(|ui| {
|
||||
for kind in ["minimize", "maximize", "close"] {
|
||||
let (r, resp) = ui.allocate_exact_size(egui::vec2(CONTROL_W, CONTROL_TITLE_H), egui::Sense::click());
|
||||
let label = app.ui.language.tr(match kind {
|
||||
"minimize" => "Minimize",
|
||||
"maximize" if maximized => "Restore Down",
|
||||
"maximize" => "Maximize",
|
||||
_ => "Close",
|
||||
});
|
||||
let hot = resp.hovered() || resp.is_pointer_button_down_on();
|
||||
if hot {
|
||||
ui.painter().rect_filled(
|
||||
r,
|
||||
0.0,
|
||||
if kind == "close" {
|
||||
egui::Color32::from_rgb(0xC4, 0x2B, 0x1C)
|
||||
} else if resp.is_pointer_button_down_on() {
|
||||
t.pressed
|
||||
} else {
|
||||
t.hover
|
||||
},
|
||||
);
|
||||
}
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, label));
|
||||
let m = r.center();
|
||||
let stroke = egui::Stroke::new(1.0, if kind == "close" && hot { egui::Color32::WHITE } else { t.text });
|
||||
match kind {
|
||||
"minimize" => {
|
||||
ui.painter().line_segment([egui::pos2(m.x - 5.0, m.y), egui::pos2(m.x + 5.0, m.y)], stroke);
|
||||
}
|
||||
"maximize" if maximized => {
|
||||
let front = egui::Rect::from_min_size(egui::pos2(m.x - 5.0, m.y - 3.0), egui::vec2(8.0, 8.0));
|
||||
ui.painter().rect_stroke(front, 0.0, stroke, egui::StrokeKind::Inside);
|
||||
ui.painter().line_segment([egui::pos2(m.x - 3.0, m.y - 5.0), egui::pos2(m.x + 5.0, m.y - 5.0)], stroke);
|
||||
ui.painter().line_segment([egui::pos2(m.x + 5.0, m.y - 5.0), egui::pos2(m.x + 5.0, m.y + 3.0)], stroke);
|
||||
}
|
||||
"maximize" => {
|
||||
ui.painter().rect_stroke(r.shrink(15.0), 0.0, stroke, egui::StrokeKind::Inside);
|
||||
}
|
||||
_ => {
|
||||
ui.painter().line_segment([m - egui::vec2(5.0, 5.0), m + egui::vec2(5.0, 5.0)], stroke);
|
||||
ui.painter().line_segment([m + egui::vec2(5.0, -5.0), m - egui::vec2(5.0, -5.0)], stroke);
|
||||
}
|
||||
}
|
||||
if resp.on_hover_text(label).clicked() {
|
||||
match kind {
|
||||
"minimize" => app.run_or_alert("ui.window.minimize", json!({})),
|
||||
"maximize" => app.run_or_alert("ui.window.maximize", json!({"on": !maximized})),
|
||||
"close" => app.run_or_alert("ui.window.close", json!({})),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if maximized {
|
||||
return;
|
||||
}
|
||||
let Some(pos) = ctx.input(|i| i.pointer.hover_pos()) else {
|
||||
return;
|
||||
};
|
||||
let l = pos.x <= screen.min.x + RESIZE_BAND;
|
||||
let r = pos.x >= screen.max.x - RESIZE_BAND;
|
||||
let top = pos.y <= screen.min.y + RESIZE_BAND;
|
||||
let b = pos.y >= screen.max.y - RESIZE_BAND;
|
||||
let dir = match (l, r, top, b) {
|
||||
(true, false, true, false) => Some(egui::viewport::ResizeDirection::NorthWest),
|
||||
(false, true, true, false) => Some(egui::viewport::ResizeDirection::NorthEast),
|
||||
(true, false, false, true) => Some(egui::viewport::ResizeDirection::SouthWest),
|
||||
(false, true, false, true) => Some(egui::viewport::ResizeDirection::SouthEast),
|
||||
(true, false, _, _) => Some(egui::viewport::ResizeDirection::West),
|
||||
(false, true, _, _) => Some(egui::viewport::ResizeDirection::East),
|
||||
(_, _, true, false) => Some(egui::viewport::ResizeDirection::North),
|
||||
(_, _, false, true) => Some(egui::viewport::ResizeDirection::South),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(dir) = dir
|
||||
&& ctx.input(|i| i.pointer.primary_pressed())
|
||||
&& !controls.contains(pos)
|
||||
{
|
||||
let direction = match dir {
|
||||
egui::ResizeDirection::North => "north",
|
||||
egui::ResizeDirection::South => "south",
|
||||
egui::ResizeDirection::East => "east",
|
||||
egui::ResizeDirection::West => "west",
|
||||
egui::ResizeDirection::NorthEast => "northeast",
|
||||
egui::ResizeDirection::NorthWest => "northwest",
|
||||
egui::ResizeDirection::SouthEast => "southeast",
|
||||
egui::ResizeDirection::SouthWest => "southwest",
|
||||
};
|
||||
app.run_or_alert("ui.window.beginResize", json!({"direction": direction}));
|
||||
}
|
||||
app.flush_viewport_commands(ctx);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod title_bar_tests {
|
||||
use super::*;
|
||||
|
||||
Reference in new issue
Block a user