zng_wgt_window/
fallback_chrome.rs

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