pixel_widgets/widget/
window.rs

1use crate::draw::*;
2use crate::event::{Event, Key};
3use crate::layout::{Rectangle, Size};
4use crate::node::{GenericNode, IntoNode, Node};
5use crate::style::Stylesheet;
6use crate::widget::{Context, Widget};
7
8/// A window with a title and a content widget that can be moved by dragging the title.
9pub struct Window<'a, T> {
10    title: Option<Node<'a, T>>,
11    content: Option<Node<'a, T>>,
12}
13
14/// State for [`Window`](struct.Window.html)
15pub struct State {
16    x: f32,
17    y: f32,
18    cursor_x: f32,
19    cursor_y: f32,
20    inner: InnerState,
21}
22
23#[derive(Clone, Copy)]
24enum InnerState {
25    Idle,
26    Dragging(f32, f32),
27}
28
29impl<'a, T: 'a> Window<'a, T> {
30    /// Constructs a new `Window`
31    pub fn new(title: impl IntoNode<'a, T>, content: impl IntoNode<'a, T>) -> Self {
32        Self {
33            title: Some(title.into_node()),
34            content: Some(content.into_node()),
35        }
36    }
37
38    /// Sets the title bar widget from the first element of the iterator.
39    /// Sets the content widget from the second element of the iterator.
40    pub fn extend<I: IntoIterator<Item = N>, N: IntoNode<'a, T>>(mut self, iter: I) -> Self {
41        let mut iter = iter.into_iter();
42        if self.title.is_none() {
43            self.title = iter.next().map(IntoNode::into_node);
44        }
45        if self.content.is_none() {
46            self.content = iter.next().map(IntoNode::into_node);
47        }
48        self
49    }
50
51    fn layout(&self, state: &State, viewport: Rectangle, style: &Stylesheet) -> (Rectangle, Rectangle, Rectangle) {
52        let title_size = self.title().size();
53        let title_width = title_size.0.min_size();
54        let title_height = title_size.1.min_size();
55        let content_size = self.content().size();
56        let content_width = content_size.0.min_size();
57        let content_height = content_size.1.min_size();
58        let width = title_width.max(content_width);
59        let height = title_height + content_height;
60        let padding = style.background.padding();
61        let padding = Rectangle {
62            left: padding.left + style.padding.left,
63            right: padding.right + style.padding.right,
64            top: padding.top + style.padding.top,
65            bottom: padding.bottom + style.padding.bottom,
66        };
67        let layout = Rectangle::from_xywh(
68            viewport.left + state.x,
69            viewport.top + state.y,
70            width + padding.left + padding.right,
71            height + padding.top + padding.bottom,
72        );
73        let title_content = layout.after_padding(padding);
74        let title = Rectangle::from_xywh(
75            title_content.left,
76            title_content.top,
77            title_size.0.resolve(title_content.width(), title_size.0.parts()),
78            title_height,
79        );
80        let content = Rectangle::from_xywh(
81            title_content.left,
82            title_content.top + title_height,
83            content_size.0.resolve(title_content.width(), content_size.0.parts()),
84            content_height,
85        );
86        let align = |rect: Rectangle| {
87            rect.translate(
88                style
89                    .align_horizontal
90                    .resolve_start(rect.width(), title_content.width()),
91                0.0,
92            )
93        };
94        (layout, align(title), align(content))
95    }
96
97    fn content(&self) -> &Node<'a, T> {
98        self.content.as_ref().expect("content of `Window` must be set")
99    }
100
101    fn content_mut(&mut self) -> &mut Node<'a, T> {
102        self.content.as_mut().expect("content of `Window` must be set")
103    }
104
105    fn title(&self) -> &Node<'a, T> {
106        self.title.as_ref().expect("title of `Window` must be set")
107    }
108
109    fn title_mut(&mut self) -> &mut Node<'a, T> {
110        self.title.as_mut().expect("title of `Window` must be set")
111    }
112}
113
114impl<'a, T: 'a> Default for Window<'a, T> {
115    fn default() -> Self {
116        Self {
117            title: None,
118            content: None,
119        }
120    }
121}
122
123impl<'a, T: 'a> Widget<'a, T> for Window<'a, T> {
124    type State = State;
125
126    fn mount(&self) -> Self::State {
127        State::default()
128    }
129
130    fn widget(&self) -> &'static str {
131        "window"
132    }
133
134    fn len(&self) -> usize {
135        2
136    }
137
138    fn visit_children(&mut self, visitor: &mut dyn FnMut(&mut dyn GenericNode<'a, T>)) {
139        visitor(&mut **self.title_mut());
140        visitor(&mut **self.content_mut());
141    }
142
143    fn size(&self, _: &State, _: &Stylesheet) -> (Size, Size) {
144        (Size::Fill(1), Size::Fill(1))
145    }
146
147    fn hit(&self, state: &State, viewport: Rectangle, clip: Rectangle, style: &Stylesheet, x: f32, y: f32) -> bool {
148        if clip.point_inside(x, y) {
149            let (layout, _, _) = self.layout(state, viewport, style);
150            layout.point_inside(x, y)
151        } else {
152            false
153        }
154    }
155
156    fn focused(&self, _: &State) -> bool {
157        self.title().focused() || self.content().focused()
158    }
159
160    fn event(
161        &mut self,
162        state: &mut State,
163        viewport: Rectangle,
164        clip: Rectangle,
165        style: &Stylesheet,
166        event: Event,
167        context: &mut Context<T>,
168    ) {
169        let (layout, title, content) = self.layout(&*state, viewport, style);
170
171        if self.title().focused() {
172            self.title_mut().event(title, clip, event, context);
173            return;
174        }
175
176        if self.content().focused() {
177            self.content_mut().event(content, clip, event, context);
178            return;
179        }
180
181        match (event, state.inner) {
182            (Event::Cursor(x, y), InnerState::Idle) => {
183                state.cursor_x = x;
184                state.cursor_y = y;
185            }
186
187            (Event::Press(Key::LeftMouseButton), InnerState::Idle) => {
188                if clip.point_inside(state.cursor_x, state.cursor_y)
189                    && title.point_inside(state.cursor_x, state.cursor_y)
190                {
191                    context.redraw();
192                    state.inner = InnerState::Dragging(state.cursor_x - layout.left, state.cursor_y - layout.top);
193                }
194            }
195
196            (Event::Cursor(x, y), InnerState::Dragging(anchor_x, anchor_y)) => {
197                context.redraw();
198                state.cursor_x = x;
199                state.cursor_y = y;
200                state.x = (x - anchor_x).max(0.0).min(viewport.width() - layout.width());
201                state.y = (y - anchor_y).max(0.0).min(viewport.height() - layout.height());
202            }
203
204            (Event::Release(Key::LeftMouseButton), InnerState::Dragging(_, _)) => {
205                state.inner = InnerState::Idle;
206            }
207
208            _ => (),
209        }
210
211        self.title_mut().event(title, clip, event, context);
212        self.content_mut().event(content, clip, event, context);
213    }
214
215    fn draw(
216        &mut self,
217        state: &mut State,
218        viewport: Rectangle,
219        clip: Rectangle,
220        style: &Stylesheet,
221    ) -> Vec<Primitive<'a>> {
222        let (layout, title, content) = self.layout(&*state, viewport, style);
223
224        let mut result = Vec::new();
225        result.extend(style.background.render(layout));
226        result.extend(self.title_mut().draw(title, clip));
227        result.extend(self.content_mut().draw(content, clip));
228        result
229    }
230}
231
232impl<'a, T: 'a> IntoNode<'a, T> for Window<'a, T> {
233    fn into_node(self) -> Node<'a, T> {
234        Node::from_widget(self)
235    }
236}
237
238impl Default for State {
239    fn default() -> Self {
240        Self {
241            x: 0.0,
242            y: 0.0,
243            cursor_x: 0.0,
244            cursor_y: 0.0,
245            inner: InnerState::Idle,
246        }
247    }
248}