zng_wgt_window/
fallback_chrome.rs

1use zng_ext_input::mouse::*;
2use zng_ext_window::{cmd::*, *};
3use zng_wgt::{prelude::*, *};
4use zng_wgt_button::Button;
5use zng_wgt_container::*;
6use zng_wgt_fill::*;
7use zng_wgt_input::{mouse::*, *};
8use zng_wgt_stack::{Stack, StackDirection};
9use zng_wgt_text::Text;
10
11/// Custom window chrome adorner used when the window manager does not provide one.
12///
13/// You also must set a padding of `5` for maximized window and `(28 + 5, 5, 5, 5)` for normal window.
14pub fn fallback_chrome() -> impl UiNode {
15    let vars = WINDOW.vars();
16    let can_move = vars.state().map(|s| matches!(s, WindowState::Normal | WindowState::Maximized));
17    let win_id = WINDOW.id();
18    let title = Text! {
19        txt = vars.title();
20        align = Align::FILL_TOP;
21        background_color = light_dark(colors::WHITE, colors::BLACK);
22        zng_wgt_size_offset::height = 28;
23        txt_align = Align::CENTER;
24
25        child_right = Stack! {
26            direction = StackDirection::left_to_right();
27            zng_wgt_button::style_fn = zng_wgt_button::LightStyle! {
28                corner_radius = 0;
29                zng_wgt_button::cmd_child_fn = wgt_fn!(|cmd: Command| {
30                    presenter((), cmd.icon().map(move |ico| wgt_fn!(ico, |_| {
31                        let ico = ico(());
32                        if ico.is_nil() {
33                            // fallback to Unicode symbol
34                            let cmd = cmd.scoped(zng_app::event::CommandScope::App);
35                            let (symbol, size, padding_top) = if cmd == RESTORE_CMD {
36                                ("🗗", 9, 0)
37                            } else if cmd == MINIMIZE_CMD {
38                                ("🗕", 9, 0)
39                            } else if cmd == MAXIMIZE_CMD {
40                                ("🗖", 9, 0)
41                            } else if cmd == CLOSE_CMD {
42                                ("🗙", 12, -5)
43                            } else {
44                                unreachable!("{cmd:?} what")
45                            };
46                            Text! {
47                                font_family = "Noto Sans Symbols 2";
48                                font_size = size.pt();
49                                padding = (padding_top, 0, 0, 0);
50                                txt = symbol;
51                            }.boxed()
52                        } else {
53                            ico
54                        }
55                    })))
56                });
57            };
58            children = ui_vec![
59                Button! {
60                    cmd = MINIMIZE_CMD.scoped(win_id);
61                },
62                Button! {
63                    cmd = MAXIMIZE_CMD.scoped(win_id);
64                    when #is_disabled {
65                        visibility = false;
66                    }
67                },
68                Button! {
69                    cmd = RESTORE_CMD.scoped(win_id);
70                    when #is_disabled {
71                        visibility = false;
72                    }
73                },
74                Button! {
75                    cmd = CLOSE_CMD.scoped(win_id);
76                },
77            ];
78        }, 0;
79
80        when *#{can_move.clone()} {
81            cursor = CursorIcon::Move;
82        }
83        mouse::on_mouse_down = hn!(|args: &MouseInputArgs| {
84            if args.is_primary() && can_move.get() && args.target.widget_id() == WIDGET.id() {
85                DRAG_MOVE_RESIZE_CMD.scoped(WINDOW.id()).notify();
86            }
87        });
88
89        gesture::on_context_click = hn!(|args: &gesture::ClickArgs| {
90            if matches!(WINDOW.vars().state().get(), WindowState::Normal | WindowState::Maximized) && args.target.widget_id() == WIDGET.id() {
91                if let Some(p) = args.position() {
92                    OPEN_TITLE_BAR_CONTEXT_MENU_CMD.scoped(WINDOW.id()).notify_param(p);
93                }
94            }
95        });
96    };
97
98    use zng_ext_window::cmd::ResizeDirection as RD;
99
100    fn resize_direction(wgt_pos: PxPoint) -> Option<RD> {
101        let p = wgt_pos;
102        let s = WIDGET.bounds().inner_size();
103        let b = WIDGET.border().offsets();
104        let corner_b = b * FactorSideOffsets::from(3.fct());
105
106        if p.x <= b.left {
107            if p.y <= corner_b.top {
108                Some(RD::NorthWest)
109            } else if p.y >= s.height - corner_b.bottom {
110                Some(RD::SouthWest)
111            } else {
112                Some(RD::West)
113            }
114        } else if p.x >= s.width - b.right {
115            if p.y <= corner_b.top {
116                Some(RD::NorthEast)
117            } else if p.y >= s.height - corner_b.bottom {
118                Some(RD::SouthEast)
119            } else {
120                Some(RD::East)
121            }
122        } else if p.y <= b.top {
123            if p.x <= corner_b.left {
124                Some(RD::NorthWest)
125            } else if p.x >= s.width - corner_b.right {
126                Some(RD::NorthEast)
127            } else {
128                Some(RD::North)
129            }
130        } else if p.y >= s.height - b.bottom {
131            if p.x <= corner_b.left {
132                Some(RD::SouthWest)
133            } else if p.x >= s.width - corner_b.right {
134                Some(RD::SouthEast)
135            } else {
136                Some(RD::South)
137            }
138        } else {
139            None
140        }
141    }
142
143    let cursor = var(CursorSource::Hidden);
144
145    Container! {
146        exclude_text_context = true;
147
148        hit_test_mode = HitTestMode::Detailed;
149
150        child = title;
151
152        when matches!(#{vars.state()}, WindowState::Normal) {
153            border = 5, light_dark(colors::WHITE, colors::BLACK).rgba().map_into();
154            cursor = cursor.clone();
155            on_mouse_move = hn!(|args: &MouseMoveArgs| {
156                cursor.set(match args.position_wgt().and_then(resize_direction) {
157                    Some(d) => CursorIcon::from(d).into(),
158                    None => CursorSource::Hidden,
159                });
160            });
161            on_mouse_down = hn!(|args: &MouseInputArgs| {
162                if args.is_primary() {
163                    if let Some(d) = args.position_wgt().and_then(resize_direction) {
164                        DRAG_MOVE_RESIZE_CMD.scoped(WINDOW.id()).notify_param(d);
165                    }
166                }
167            });
168        }
169    }
170}
171
172#[property(WIDGET)]
173fn exclude_text_context(child: impl UiNode, exclude: impl IntoValue<bool>) -> impl UiNode {
174    assert!(exclude.into());
175
176    // exclude all text context vars set on the window
177    let excluded_set = {
178        let mut c = ContextValueSet::new();
179        Text::context_vars_set(&mut c);
180        c
181    };
182    let child = match_node(child, move |c, op| {
183        let mut filtered = LocalContext::capture_filtered(CaptureFilter::Exclude(excluded_set.clone()));
184        filtered.with_context(|| {
185            c.op(op);
186        });
187    });
188
189    // override layout font size
190    zng_wgt_text::font_size(child, 16)
191}