kas_core/
decorations.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Window title-bar and border decorations
7//!
8//! Note: due to definition in kas-core, some widgets must be duplicated.
9
10use crate::event::{CursorIcon, ResizeDirection};
11use crate::prelude::*;
12use crate::theme::MarkStyle;
13use crate::theme::{Text, TextClass};
14use kas_macros::impl_scope;
15use std::fmt::Debug;
16
17/// Available decoration modes
18///
19/// See [`Window::decorations`].
20#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
21pub enum Decorations {
22    /// No decorations
23    ///
24    /// The root widget is drawn as a simple rectangle with no borders.
25    None,
26    /// Add a simple themed border to the widget
27    ///
28    /// Probably looks better if [`Window::transparent`] is true.
29    Border,
30    /// Toolkit-drawn decorations
31    ///
32    /// Decorations will match the toolkit theme, not the platform theme.
33    /// These decorations may not have all the same capabilities.
34    ///
35    /// Probably looks better if [`Window::transparent`] is true.
36    Toolkit,
37    /// Server-side decorations
38    ///
39    /// Decorations are drawn by the window manager, if available.
40    Server,
41}
42
43impl_scope! {
44    /// A border region
45    ///
46    /// Does not draw anything; used solely for event handling.
47    #[widget {
48        cursor_icon = self.cursor_icon();
49    }]
50    pub(crate) struct Border {
51        core: widget_core!(),
52        resizable: bool,
53        direction: ResizeDirection,
54    }
55
56    impl Self {
57        pub fn new(direction: ResizeDirection) -> Self {
58            Border {
59                core: Default::default(),
60                resizable: true,
61                direction,
62            }
63        }
64
65        pub fn set_resizable(&mut self, resizable: bool) {
66            self.resizable = resizable;
67        }
68
69        fn cursor_icon(&self) -> CursorIcon {
70            if self.resizable {
71                self.direction.into()
72            } else {
73                CursorIcon::default()
74            }
75        }
76    }
77
78    impl Layout for Self {
79        fn size_rules(&mut self, _: SizeCx, _axis: AxisInfo) -> SizeRules {
80            SizeRules::EMPTY
81        }
82
83        fn draw(&mut self, _: DrawCx) {}
84    }
85
86    impl Events for Self {
87        type Data = ();
88
89        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
90            match event {
91                Event::PressStart { .. } => {
92                    cx.drag_resize_window(self.direction);
93                    Used
94                }
95                _ => Unused,
96            }
97        }
98    }
99}
100
101impl_scope! {
102    /// A simple label
103    #[derive(Clone, Debug, Default)]
104    #[widget {
105        Data = ();
106    }]
107    pub(crate) struct Label {
108        core: widget_core!(),
109        text: Text<String>,
110    }
111
112    impl Self {
113        /// Construct from `text`
114        #[inline]
115        fn new(text: impl ToString) -> Self {
116            Label {
117                core: Default::default(),
118                text: Text::new(text.to_string(), TextClass::Label(false)),
119            }
120        }
121    }
122
123    impl Layout for Self {
124        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
125            sizer.text_rules(&mut self.text, axis)
126        }
127
128        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
129            self.core.rect = rect;
130            let align = hints.complete(Align::Center, Align::Center);
131            cx.text_set_size(&mut self.text, rect.size, align);
132        }
133
134        fn draw(&mut self, mut draw: DrawCx) {
135            draw.text(self.rect(), &self.text);
136        }
137    }
138
139    impl Events for Self {
140        fn configure(&mut self, cx: &mut ConfigCx) {
141            cx.text_configure(&mut self.text);
142        }
143    }
144
145    impl HasStr for Self {
146        fn get_str(&self) -> &str {
147            self.text.as_str()
148        }
149    }
150
151    impl HasString for Self {
152        fn set_string(&mut self, string: String) -> Action {
153            self.text.set_string(string);
154            self.text.reprepare_action()
155        }
156    }
157}
158
159impl_scope! {
160    /// A mark which is also a button
161    ///
162    /// This button is not keyboard navigable; only mouse/touch interactive.
163    ///
164    /// Uses stretch policy [`Stretch::Low`].
165    #[derive(Clone, Debug)]
166    #[widget {
167        hover_highlight = true;
168    }]
169    pub(crate) struct MarkButton<M: Clone + Debug + 'static> {
170        core: widget_core!(),
171        style: MarkStyle,
172        msg: M,
173    }
174
175    impl Self {
176        /// Construct
177        ///
178        /// A clone of `msg` is sent as a message on click.
179        pub fn new(style: MarkStyle, msg: M) -> Self {
180            MarkButton {
181                core: Default::default(),
182                style,
183                msg,
184            }
185        }
186    }
187
188    impl Layout for Self {
189        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
190            sizer.feature(self.style.into(), axis)
191        }
192
193        fn draw(&mut self, mut draw: DrawCx) {
194            draw.mark(self.core.rect, self.style);
195        }
196    }
197
198    impl Events for Self {
199        type Data = ();
200
201        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
202            event.on_activate(cx, self.id(), |cx| {
203                cx.push(self.msg.clone());
204                Used
205            })
206        }
207    }
208}
209
210#[derive(Copy, Clone, Debug)]
211enum TitleBarButton {
212    Minimize,
213    Maximize,
214    Close,
215}
216
217impl_scope! {
218    /// A set of title-bar buttons
219    ///
220    /// Currently, this consists of minimise, maximise and close buttons.
221    #[derive(Clone, Default)]
222    #[widget{
223        layout = row! [
224            MarkButton::new(MarkStyle::Point(Direction::Down), TitleBarButton::Minimize),
225            MarkButton::new(MarkStyle::Point(Direction::Up), TitleBarButton::Maximize),
226            MarkButton::new(MarkStyle::X, TitleBarButton::Close),
227        ];
228    }]
229    pub struct TitleBarButtons {
230        core: widget_core!(),
231    }
232
233    impl Self {
234        /// Construct
235        #[inline]
236        pub fn new() -> Self {
237            TitleBarButtons {
238                core: Default::default(),
239            }
240        }
241    }
242
243    impl Events for Self {
244        type Data = ();
245
246        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
247            if let Some(msg) = cx.try_pop() {
248                match msg {
249                    TitleBarButton::Minimize => {
250                        #[cfg(winit)]
251                        if let Some(w) = cx.winit_window() {
252                            w.set_minimized(true);
253                        }
254                    }
255                    TitleBarButton::Maximize => {
256                        #[cfg(winit)]
257                        if let Some(w) = cx.winit_window() {
258                            w.set_maximized(!w.is_maximized());
259                        }
260                    }
261                    TitleBarButton::Close => cx.action(self, Action::CLOSE),
262                }
263            }
264        }
265    }
266}
267
268impl_scope! {
269    /// A window's title bar (part of decoration)
270    #[derive(Clone, Default)]
271    #[widget{
272        layout = row! [
273            // self.icon,
274            self.title,
275            self.buttons,
276        ];
277    }]
278    pub struct TitleBar {
279        core: widget_core!(),
280        #[widget]
281        title: Label,
282        #[widget]
283        buttons: TitleBarButtons,
284    }
285
286    impl Self {
287        /// Construct a title bar
288        #[inline]
289        pub fn new(title: impl ToString) -> Self {
290            TitleBar {
291                core: Default::default(),
292                title: Label::new(title),
293                buttons: Default::default(),
294            }
295        }
296
297        /// Get the title
298        pub fn title(&self) -> &str {
299            self.title.text.as_str()
300        }
301
302        /// Set the title
303        pub fn set_title(&mut self, title: String) -> Action {
304            self.title.set_string(title)
305        }
306    }
307
308    impl Events for Self {
309        type Data = ();
310
311        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
312            match event {
313                Event::PressStart { .. } => {
314                    cx.drag_window();
315                    Used
316                }
317                _ => Unused,
318            }
319        }
320    }
321}