zng_wgt_window/
fallback_chrome.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
use zng_ext_input::mouse::*;
use zng_ext_window::{cmd::*, *};
use zng_wgt::{prelude::*, *};
use zng_wgt_button::Button;
use zng_wgt_container::*;
use zng_wgt_fill::*;
use zng_wgt_input::{mouse::*, *};
use zng_wgt_stack::{Stack, StackDirection};
use zng_wgt_text::Text;

/// Custom window chrome adorner used when the window manager does not provide one.
///
/// You also must set a padding of `5` for maximized window and `(28 + 5, 5, 5, 5)` for normal window.
pub fn fallback_chrome() -> impl UiNode {
    let vars = WINDOW.vars();
    let can_move = vars.state().map(|s| matches!(s, WindowState::Normal | WindowState::Maximized));
    let win_id = WINDOW.id();
    let title = Text! {
        txt = vars.title();
        align = Align::FILL_TOP;
        background_color = light_dark(colors::WHITE, colors::BLACK);
        zng_wgt_size_offset::height = 28;
        txt_align = Align::CENTER;

        child_right = Stack! {
            direction = StackDirection::left_to_right();
            zng_wgt_button::style_fn = zng_wgt_button::LightStyle! {
                corner_radius = 0;
                zng_wgt_button::cmd_child_fn = wgt_fn!(|cmd: Command| {
                    presenter((), cmd.icon().map(move |ico| wgt_fn!(ico, |_| {
                        let ico = ico(());
                        if ico.is_nil() {
                            // fallback to Unicode symbol
                            let cmd = cmd.scoped(zng_app::event::CommandScope::App);
                            let (symbol, size, padding_top) = if cmd == RESTORE_CMD {
                                ("🗗", 9, 0)
                            } else if cmd == MINIMIZE_CMD {
                                ("🗕", 9, 0)
                            } else if cmd == MAXIMIZE_CMD {
                                ("🗖", 9, 0)
                            } else if cmd == CLOSE_CMD {
                                ("🗙", 12, -5)
                            } else {
                                unreachable!("{cmd:?} what")
                            };
                            Text! {
                                font_family = "Noto Sans Symbols 2";
                                font_size = size.pt();
                                padding = (padding_top, 0, 0, 0);
                                txt = symbol;
                            }.boxed()
                        } else {
                            ico
                        }
                    })))
                });
            };
            children = ui_vec![
                Button! {
                    cmd = MINIMIZE_CMD.scoped(win_id);
                },
                Button! {
                    cmd = MAXIMIZE_CMD.scoped(win_id);
                    when #is_disabled {
                        visibility = false;
                    }
                },
                Button! {
                    cmd = RESTORE_CMD.scoped(win_id);
                    when #is_disabled {
                        visibility = false;
                    }
                },
                Button! {
                    cmd = CLOSE_CMD.scoped(win_id);
                },
            ];
        }, 0;

        when *#{can_move.clone()} {
            cursor = CursorIcon::Move;
        }
        mouse::on_mouse_down = hn!(|args: &MouseInputArgs| {
            if args.is_primary() && can_move.get() && args.target.widget_id() == WIDGET.id() {
                DRAG_MOVE_RESIZE_CMD.scoped(WINDOW.id()).notify();
            }
        });

        gesture::on_context_click = hn!(|args: &gesture::ClickArgs| {
            if matches!(WINDOW.vars().state().get(), WindowState::Normal | WindowState::Maximized) && args.target.widget_id() == WIDGET.id() {
                if let Some(p) = args.position() {
                    OPEN_TITLE_BAR_CONTEXT_MENU_CMD.scoped(WINDOW.id()).notify_param(p);
                }
            }
        });
    };

    use zng_ext_window::cmd::ResizeDirection as RD;

    fn resize_direction(wgt_pos: PxPoint) -> Option<RD> {
        let p = wgt_pos;
        let s = WIDGET.bounds().inner_size();
        let b = WIDGET.border().offsets();
        let corner_b = b * FactorSideOffsets::from(3.fct());

        if p.x <= b.left {
            if p.y <= corner_b.top {
                Some(RD::NorthWest)
            } else if p.y >= s.height - corner_b.bottom {
                Some(RD::SouthWest)
            } else {
                Some(RD::West)
            }
        } else if p.x >= s.width - b.right {
            if p.y <= corner_b.top {
                Some(RD::NorthEast)
            } else if p.y >= s.height - corner_b.bottom {
                Some(RD::SouthEast)
            } else {
                Some(RD::East)
            }
        } else if p.y <= b.top {
            if p.x <= corner_b.left {
                Some(RD::NorthWest)
            } else if p.x >= s.width - corner_b.right {
                Some(RD::NorthEast)
            } else {
                Some(RD::North)
            }
        } else if p.y >= s.height - b.bottom {
            if p.x <= corner_b.left {
                Some(RD::SouthWest)
            } else if p.x >= s.width - corner_b.right {
                Some(RD::SouthEast)
            } else {
                Some(RD::South)
            }
        } else {
            None
        }
    }

    let cursor = var(CursorSource::Hidden);

    Container! {
        exclude_text_context = true;

        hit_test_mode = HitTestMode::Detailed;

        child = title;

        when matches!(#{vars.state()}, WindowState::Normal) {
            border = 5, light_dark(colors::WHITE, colors::BLACK).rgba().map_into();
            cursor = cursor.clone();
            on_mouse_move = hn!(|args: &MouseMoveArgs| {
                cursor.set(match args.position_wgt().and_then(resize_direction) {
                    Some(d) => CursorIcon::from(d).into(),
                    None => CursorSource::Hidden,
                });
            });
            on_mouse_down = hn!(|args: &MouseInputArgs| {
                if args.is_primary() {
                    if let Some(d) = args.position_wgt().and_then(resize_direction) {
                        DRAG_MOVE_RESIZE_CMD.scoped(WINDOW.id()).notify_param(d);
                    }
                }
            });
        }
    }
}

#[property(WIDGET)]
fn exclude_text_context(child: impl UiNode, exclude: impl IntoValue<bool>) -> impl UiNode {
    assert!(exclude.into());

    // exclude all text context vars set on the window
    let excluded_set = {
        let mut c = ContextValueSet::new();
        Text::context_vars_set(&mut c);
        c
    };
    let child = match_node(child, move |c, op| {
        let mut filtered = LocalContext::capture_filtered(CaptureFilter::Exclude(excluded_set.clone()));
        filtered.with_context(|| {
            c.op(op);
        });
    });

    // override layout font size
    zng_wgt_text::font_size(child, 16)
}