Skip to content

Commit 945a034

Browse files
committed
nuon: Split stuff into modules as we go towards being real library
1 parent ef7784b commit 945a034

File tree

5 files changed

+413
-404
lines changed

5 files changed

+413
-404
lines changed

nuon/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Nuon is the most bare bones UI library you can imagine (by design)
2+
3+
Features:
4+
- Lack of widgets
5+
- Lack of trees and hierarchies
6+
- Lack of rendering architecture of any kind
7+
- Basic layouting helpers
8+
- Handles mouse events for given set of elements and their bounding boxes
9+
10+
TODO:
11+
- Handle focus and keyboard events as well
12+

nuon/src/elements_map.rs

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
use crate::{Point, Rect, Size};
2+
3+
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
4+
pub struct ElementId(thunderdome::Index);
5+
6+
pub struct ElementBuilder<M> {
7+
name: Option<&'static str>,
8+
on_click: Option<M>,
9+
on_pressed: Option<M>,
10+
on_release: Option<M>,
11+
on_cursor_move: Option<M>,
12+
rect: Rect,
13+
}
14+
15+
impl<M> Default for ElementBuilder<M> {
16+
fn default() -> Self {
17+
Self::new()
18+
}
19+
}
20+
21+
impl<M> ElementBuilder<M> {
22+
pub fn new() -> Self {
23+
Self {
24+
name: None,
25+
on_click: None,
26+
on_pressed: None,
27+
on_release: None,
28+
on_cursor_move: None,
29+
rect: Rect::zero(),
30+
}
31+
}
32+
33+
pub fn name(mut self, name: &'static str) -> Self {
34+
self.name = Some(name);
35+
self
36+
}
37+
38+
pub fn on_click(mut self, msg: M) -> Self {
39+
self.on_click = Some(msg);
40+
self
41+
}
42+
43+
pub fn on_pressed(mut self, msg: M) -> Self {
44+
self.on_pressed = Some(msg);
45+
self
46+
}
47+
48+
pub fn on_release(mut self, msg: M) -> Self {
49+
self.on_release = Some(msg);
50+
self
51+
}
52+
53+
pub fn on_cursor_move(mut self, msg: M) -> Self {
54+
self.on_cursor_move = Some(msg);
55+
self
56+
}
57+
58+
pub fn rect(mut self, rect: Rect) -> Self {
59+
self.rect = rect;
60+
self
61+
}
62+
63+
pub fn position(mut self, pos: Point) -> Self {
64+
self.rect.origin = pos;
65+
self
66+
}
67+
68+
pub fn size(mut self, size: Size) -> Self {
69+
self.rect.size = size;
70+
self
71+
}
72+
73+
fn build(self) -> Element<M> {
74+
Element {
75+
name: self.name.unwrap_or("Element"),
76+
on_click: self.on_click,
77+
on_pressed: self.on_pressed,
78+
on_release: self.on_release,
79+
on_cursor_move: self.on_cursor_move,
80+
hovered: false,
81+
rect: self.rect,
82+
}
83+
}
84+
}
85+
86+
#[derive(Debug)]
87+
pub struct Element<M> {
88+
name: &'static str,
89+
/// Button-like click, needs both press and release without focus loss
90+
on_click: Option<M>,
91+
on_pressed: Option<M>,
92+
on_release: Option<M>,
93+
on_cursor_move: Option<M>,
94+
hovered: bool,
95+
rect: Rect,
96+
}
97+
98+
impl<M> Element<M> {
99+
pub fn name(&self) -> &'static str {
100+
self.name
101+
}
102+
103+
pub fn hovered(&self) -> bool {
104+
self.hovered
105+
}
106+
107+
pub fn rect(&self) -> Rect {
108+
self.rect
109+
}
110+
111+
pub fn on_click(&self) -> Option<&M> {
112+
self.on_click.as_ref()
113+
}
114+
115+
pub fn on_pressed(&self) -> Option<&M> {
116+
self.on_pressed.as_ref()
117+
}
118+
119+
pub fn on_release(&self) -> Option<&M> {
120+
self.on_release.as_ref()
121+
}
122+
123+
pub fn on_cursor_move(&self) -> Option<&M> {
124+
self.on_cursor_move.as_ref()
125+
}
126+
}
127+
128+
#[derive(Debug, Default)]
129+
pub struct ElementsMap<M> {
130+
elements: thunderdome::Arena<Element<M>>,
131+
zorder: Vec<ElementId>,
132+
hovered: Option<ElementId>,
133+
pressed: Option<ElementId>,
134+
mouse_grab: Option<ElementId>,
135+
}
136+
137+
impl<M> ElementsMap<M> {
138+
pub fn new() -> Self {
139+
Self {
140+
elements: thunderdome::Arena::new(),
141+
zorder: Vec::new(),
142+
hovered: None,
143+
pressed: None,
144+
mouse_grab: None,
145+
}
146+
}
147+
148+
pub fn iter(&self) -> impl Iterator<Item = (ElementId, &Element<M>)> {
149+
self.elements
150+
.iter()
151+
.map(|(id, element)| (ElementId(id), element))
152+
}
153+
154+
pub fn insert(&mut self, builder: ElementBuilder<M>) -> ElementId {
155+
let id = ElementId(self.elements.insert(builder.build()));
156+
self.listen_for_mouse(id);
157+
id
158+
}
159+
160+
pub fn update(&mut self, id: ElementId, rect: Rect) -> Option<&Element<M>> {
161+
let Some(element) = self.elements.get_mut(id.0) else {
162+
// TODO: make this debug panic with a log
163+
panic!("Element not found");
164+
};
165+
166+
element.rect = rect;
167+
Some(element)
168+
}
169+
170+
fn listen_for_mouse(&mut self, id: ElementId) {
171+
// TODO: Make this smarter
172+
self.zorder.push(id);
173+
}
174+
175+
pub fn get(&self, id: ElementId) -> Option<&Element<M>> {
176+
self.elements.get(id.0)
177+
}
178+
179+
pub fn update_cursor_pos(&mut self, point: Point) {
180+
self.hovered = None;
181+
182+
for id in self.zorder.iter().rev() {
183+
let Some(element) = self.elements.get_mut(id.0) else {
184+
continue;
185+
};
186+
187+
element.hovered = element.rect.contains(point);
188+
if self.hovered.is_none() && element.hovered {
189+
self.hovered = Some(*id);
190+
}
191+
}
192+
}
193+
194+
pub fn hovered_element_id(&self) -> Option<ElementId> {
195+
self.hovered
196+
}
197+
198+
pub fn hovered_element(&self) -> Option<(ElementId, &Element<M>)> {
199+
let id = self.hovered?;
200+
let element = self.elements.get(id.0)?;
201+
Some((id, element))
202+
}
203+
204+
pub fn set_mouse_grab(&mut self, id: Option<ElementId>) {
205+
self.mouse_grab = id;
206+
}
207+
208+
pub fn current_mouse_grab_id(&self) -> Option<ElementId> {
209+
self.mouse_grab
210+
}
211+
212+
pub fn current_mouse_grab(&self) -> Option<(ElementId, &Element<M>)> {
213+
let id = self.mouse_grab?;
214+
let element = self.elements.get(id.0)?;
215+
Some((id, element))
216+
}
217+
218+
pub fn on_press(&mut self) -> Option<(ElementId, &Element<M>)> {
219+
let id = self.hovered?;
220+
let element = self.elements.get(id.0)?;
221+
self.pressed = Some(id);
222+
Some((id, element))
223+
}
224+
225+
pub fn on_release(&mut self) -> Option<(ElementId, &Element<M>)> {
226+
let id = self.pressed.take()?;
227+
let element = self.elements.get(id.0)?;
228+
Some((id, element))
229+
}
230+
231+
pub fn element_under(&self, point: Point) -> Option<ElementId> {
232+
for (id, element) in self.elements.iter() {
233+
if element.rect.contains(point) {
234+
return Some(ElementId(id));
235+
}
236+
}
237+
238+
None
239+
}
240+
}

0 commit comments

Comments
 (0)