diff --git a/mangades/src/main.rs b/mangades/src/main.rs index a79f581..98a0a9f 100644 --- a/mangades/src/main.rs +++ b/mangades/src/main.rs @@ -1,9 +1,9 @@ use std::sync::{RwLock, Arc}; -use mangui::{self, nodes::{layout::Layout, self, Style, TaffyStyle}, taffy::{self, prelude::Size, style::Dimension}, femtovg::{Paint, Color}, CurrentRenderer, SharedNode}; +use mangui::{self, nodes::{layout::Layout, self, Style, TaffyStyle}, taffy::{self, prelude::Size, style::Dimension}, femtovg::{Paint, Color}, SharedNode}; fn main() { - let mut root = Layout::::new(); + let mut root = Layout::default(); root.style.layout.display = taffy::style::Display::Flex; root.style.layout.flex_direction = taffy::style::FlexDirection::Row; root.children.push(Arc::new(RwLock::new(nodes::primitives::Rectangle { @@ -81,7 +81,7 @@ fn main() { fill: Paint::color(Color::rgb(0, 0, 255)), radius: 0. }))); - let groot: SharedNode = Arc::new(RwLock::new(root)); + let groot: SharedNode = Arc::new(RwLock::new(root)); mangui::run_event_loop(groot); } diff --git a/ui/src/events/mod.rs b/ui/src/events/mod.rs new file mode 100644 index 0000000..072bde7 --- /dev/null +++ b/ui/src/events/mod.rs @@ -0,0 +1,231 @@ +use std::ops::{AddAssign, Add, SubAssign, Sub}; + +use taffy::{prelude::Size, style::Dimension, geometry::Point}; +use winit::event::ElementState; +pub use winit::event::{TouchPhase, MouseScrollDelta, DeviceId, ModifiersState, VirtualKeyCode, ScanCode, MouseButton}; + +use crate::SharedNode; + +pub struct NodeEvent { + /// Target node of event. + pub target: SharedNode, + /// Path to the target - target will be the last item in the path. + pub path: Vec, + /// Actual event + pub event: InnerEvent +} + +/// Different event types that can be sent to a node. +pub enum InnerEvent { + Wheel { + phase: TouchPhase, + delta: MouseScrollDelta, + mouse: MouseEvent + }, + /// Mouse enter event is fired when the mouse enters the target node or any of its children, and bubbles + MouseEnter(MouseEvent), + /// Mouse over event is fired when the mouse enters the target node, but not its children, and does not bubble + MouseOver(MouseEvent), + /// Mouse leave event is fired when the mouse leaves the target node or any of its children, and bubbles + MouseLeave(MouseEvent), + /// Mouse out event is fired when the mouse leaves the target node, but not its children, and does not bubble + MouseOut(MouseEvent), + /// Mouse moved + MouseMove(MouseEvent), + /// Mouse button pressed + MouseDown(MouseEvent), + /// Mouse button released + MouseUp(MouseEvent), + /// Mouse button clicked - fired after clicking and releasing a button without changing the target element + Click(MouseEvent), + /// Mouse secondary button (usually right) clicked + ContextMenu(MouseEvent), + /// Mouse tertiary button (usually middle or scroll wheel) clicked + AuxClick(MouseEvent), + /// Focus event is fired only on the target node and does not bubble + Focus, + /// Blur event is fired only on the target node and does not bubble + Blur, + /// Same as [InnerEvent::Focus] but bubbles + FocusIn, + /// Same as [InnerEvent::Blur] but bubbles + FocusOut, + /// Key pressed + KeyDown(KeyboardEvent), + /// Key released + KeyUp(KeyboardEvent), +} + +pub struct KeyboardEvent { + /// Logical location ("it's effect") of the key + pub key: Option, + /// Physical location of the key + pub code: ScanCode, + + // altKey: bool, + // ctrlKey: bool, + // metaKey: bool, + // shiftKey: bool, + /// modifier keys pressed (alt, ctrl, shift or meta/logo/windows) + pub modifiers: ModifiersState, + + // repeat: bool, + // char_code: u32, + // key_code: u32, + // which: u32, + /// DeviceId as passed by winit. An opaque, only useful when comparing with other events. + pub device: DeviceId +} + +pub struct MouseEvent { + /// The button which fired the event (if any) + pub button: Option, + /// The buttons which are currently pressed as a bitmask. + /// Use [MouseEvent::button_to_buttons] to convert a single button to a bitmask. + pub buttons: u8, + + // altKey: bool, + // ctrlKey: bool, + // metaKey: bool, + // shiftKey: bool, + /// modifier keys pressed (alt, ctrl, shift or meta/logo/windows) + pub modifiers: ModifiersState, + + /// The location of the mouse relative to window + pub client: Location, + + /// The location of the mouse relative to last event + pub movement: Location, + + /// The location of the mouse relative to the target node (not the current node!) + /// For the first event, this will be 0, 0 + pub offset: Location, + + /// DeviceId as passed by winit. An opaque, only useful when comparing with other events. + pub device: DeviceId +} + +#[derive(Copy, Clone, Default, Debug, PartialEq)] +pub struct Location { + pub x: f32, + pub y: f32 +} + +impl AddAssign for Location { + fn add_assign(&mut self, rhs: Self) { + self.x += rhs.x; + self.y += rhs.y; + } +} + +impl Location { + pub fn new(x: f32, y: f32) -> Self { + Self { x, y } + } +} + +impl From<(f32, f32)> for Location { + fn from((x, y): (f32, f32)) -> Self { + Self { x, y } + } +} + +impl From<(f64, f64)> for Location { + fn from((x, y): (f64, f64)) -> Self { + Self { x: x as f32, y: y as f32 } + } +} + +impl From> for Location { + fn from(point: Point) -> Self { + Self { x: point.x, y: point.y } + } +} + +impl Add for Location { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y + } + } +} + +impl SubAssign for Location { + fn sub_assign(&mut self, rhs: Self) { + self.x -= rhs.x; + self.y -= rhs.y; + } +} + +impl Sub for Location { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y + } + } +} + +impl Into<(f32, f32)> for Location { + fn into(self) -> (f32, f32) { + (self.x, self.y) + } +} + +impl Into> for Location { + fn into(self) -> Size { + Size { + width: Dimension::Points(self.x as f32), + height: Dimension::Points(self.y as f32) + } + } +} + +impl MouseEvent { + /// Returns `true` if the shift key is pressed. + pub fn shift(&self) -> bool { + self.modifiers.intersects(ModifiersState::SHIFT) + } + /// Returns `true` if the control key is pressed. + pub fn ctrl(&self) -> bool { + self.modifiers.intersects(ModifiersState::CTRL) + } + /// Returns `true` if the alt key is pressed. + pub fn alt(&self) -> bool { + self.modifiers.intersects(ModifiersState::ALT) + } + /// Returns `true` if the logo key is pressed. + pub fn logo(&self) -> bool { + self.modifiers.intersects(ModifiersState::LOGO) + } + + pub fn button_to_buttons(button: MouseButton) -> u8 { + match button { + MouseButton::Left => 1, + MouseButton::Right => 2, + MouseButton::Middle => 4, + MouseButton::Other(n) => 1 << n + } + } +} + +#[derive(Default)] +pub(crate) struct MouseValue { + pub last_location: Location, + pub buttons: u8 +} + +impl MouseValue { + fn update_buttons(&mut self, button: MouseButton, state: ElementState) { + let buttons = MouseEvent::button_to_buttons(button); + match state { + ElementState::Pressed => self.buttons |= buttons, + ElementState::Released => self.buttons &= !buttons + } + } +} \ No newline at end of file diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 779ed7f..f4eac64 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -1,14 +1,17 @@ +use std::collections::HashMap; use std::num::NonZeroU32; use std::ops::Deref; use std::sync::{Arc, RwLock, Weak}; +use events::{Location, MouseValue, NodeEvent, MouseEvent}; use femtovg::renderer::OpenGl; use femtovg::{Canvas, Color}; use glutin::surface::Surface; use glutin::{context::PossiblyCurrentContext, display::Display}; use glutin_winit::DisplayBuilder; +use nodes::get_element_at; use raw_window_handle::HasRawWindowHandle; -use winit::event::{Event, WindowEvent}; +use winit::event::{Event, WindowEvent, ModifiersState, DeviceId}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; use winit::{dpi::PhysicalSize, window::Window}; @@ -27,6 +30,7 @@ use weak_table::PtrWeakKeyHashMap; use crate::nodes::{layout_recursively, Node, render_recursively, RenderContext}; pub mod nodes; +pub mod events; pub use taffy; pub use femtovg; @@ -68,11 +72,101 @@ pub fn run_event_loop(root_node: SharedNode) -> ! { let mut should_recompute = true; + let mut modifiers = ModifiersState::default(); + let focus_path: Option> = None; + let mut mouse_values: HashMap = HashMap::new(); + event_loop.run(move |event, _target, control_flow| match event { Event::WindowEvent { event, .. } => match event { - // WindowEvent::CursorMoved { position, .. } => { - // window.request_redraw(); - // } + WindowEvent::MouseWheel { device_id, delta, phase, .. } => {}, + WindowEvent::CursorMoved { device_id, position, .. } => { + let mouse_value = mouse_values.get(&device_id); + let (movement, location, buttons) = match mouse_value { + Some(mouse_value) => { + let location = (position.x, position.y).into(); + let movement = location - mouse_value.last_location; + mouse_values.insert(device_id, MouseValue { + last_location: location, + buttons: mouse_value.buttons + }); + (movement, location, mouse_value.buttons) + }, + None => { + let location = (position.x, position.y).into(); + let movement = Location::new(0., 0.); + let value = MouseValue { + last_location: location, + buttons: 0 + }; + mouse_values.insert(device_id, value); + (movement, location, Default::default()) + } + }; + + let path = get_element_at(&root, &context, location); + + if let Some(path) = path { + let target_layout = context.node_layout.get(path.last().unwrap()); + let target_layout = match target_layout { + Some(target_layout) => target_layout, + None => { return; } + }; + let target_layout = taffy.layout(target_layout.to_owned()).unwrap(); + let event = NodeEvent { + target: path.last().unwrap().clone(), + path: path.clone(), + event: events::InnerEvent::MouseMove(MouseEvent { + button: None, + buttons, + client: location, + movement, + device: device_id, + modifiers, + offset: location - target_layout.location.into() + }) + }; + } + }, + WindowEvent::DroppedFile(path) => {}, + WindowEvent::HoveredFile(path) => {}, + WindowEvent::HoveredFileCancelled => {}, + WindowEvent::Focused(focused) => { + match &focus_path { + Some(path) => { + let strong_focus_path: Option> = convert_vec_option_to_option_vec(path.iter().map(|weak| weak.upgrade()).collect()); + if matches!(strong_focus_path, None) { return; } + let strong_focus_path = strong_focus_path.unwrap(); + if strong_focus_path.len() == 0 { return; } + + let focus_event = NodeEvent { + target: strong_focus_path.last().unwrap().clone(), + path: strong_focus_path.clone(), + event: if focused { events::InnerEvent::Focus } else { events::InnerEvent::Blur } + }; + strong_focus_path.last().unwrap().write().unwrap().on_event(&focus_event.event); + + let focus_event = NodeEvent { + target: strong_focus_path.last().unwrap().clone(), + path: strong_focus_path.clone(), + event: if focused { events::InnerEvent::FocusIn } else { events::InnerEvent::FocusOut } + }; + + for node in strong_focus_path.iter().rev() { + node.write().unwrap().on_event(&focus_event.event); + } + }, + None => {} + }; + }, + WindowEvent::ModifiersChanged(new_modifiers) => { modifiers = new_modifiers; }, + WindowEvent::KeyboardInput { device_id, input, is_synthetic } => {}, + WindowEvent::MouseInput { device_id, state, button, .. } => { + let mouse_value = mouse_values.get(&device_id); + let mouse_value = match mouse_value { + Some(mouse_value) => mouse_value, + None => { return; } // Mouse move should be fired first + }; + }, WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Resized(size) => { let width: NonZeroU32 = NonZeroU32::new(size.width).unwrap(); @@ -122,6 +216,12 @@ pub fn run_event_loop(root_node: SharedNode) -> ! { }) } +/// I have no idea if there's a better way to do this in rust... +/// Found via ChatGPT (the only piece of code by chatgpt itself in this whole project as of now) +fn convert_vec_option_to_option_vec(vec: Vec>) -> Option> { + vec.into_iter().collect::>>() +} + fn create_window(event_loop: &EventLoop<()>) -> (PossiblyCurrentContext, Display, Window, Surface) { let window_builder = WindowBuilder::new() .with_inner_size(PhysicalSize::new(1000., 600.)) diff --git a/ui/src/nodes/layout.rs b/ui/src/nodes/layout.rs index 857ad15..d95fc6d 100644 --- a/ui/src/nodes/layout.rs +++ b/ui/src/nodes/layout.rs @@ -1,23 +1,19 @@ use std::fmt::{Debug, Formatter}; -use femtovg::Renderer; -use crate::nodes::{Node, NodeChildren, Overflow, Style}; -use taffy::style::{Style as TaffyStyle, Dimension}; +use crate::nodes::{Node, NodeChildren, Style}; +use taffy::style::Dimension; +/// A simple layout node which contains children. #[derive(Clone, Default)] - pub struct Layout { pub style: Style, pub children: NodeChildren } impl Layout { - pub fn new() -> Layout { + pub fn new(children: NodeChildren) -> Layout { Layout { - style: Style { - layout: TaffyStyle::default(), - overflow: Overflow::Visible - }, - children: NodeChildren::new() + style: Style::default(), + children } } } diff --git a/ui/src/nodes/mod.rs b/ui/src/nodes/mod.rs index 26756d3..b248d3d 100644 --- a/ui/src/nodes/mod.rs +++ b/ui/src/nodes/mod.rs @@ -7,6 +7,7 @@ use femtovg::{Canvas, Color}; use taffy::layout::Layout; pub use taffy::style::Style as TaffyStyle; use taffy::Taffy; +use crate::events::Location; use crate::{NodeLayoutMap, NodePtr, CurrentRenderer}; type SharedTNode = Arc>; @@ -67,6 +68,40 @@ pub trait Node: Debug { /// Is an optional function instead of another trait because of missing support for trait upcasting // TODO: When rust supports trait upcasting, make this a trait fn resize(&mut self, _width: f32, _height: f32) {} + + /// Called when an event happens on the node. This is called after the children have been called. + /// Beware! Events include a path and target with [Arc>]s, but you already have a write lock for this node! + /// Remember to check if the node is the same as self, and if it is, use self instead of the node in the path to prevent deadlocks! + fn on_event(&mut self, _event: &crate::events::InnerEvent) {} +} + +pub fn get_element_at(node: &SharedTNode, context: &RenderContext, location: Location) -> Option> { + let children = node.read().unwrap().children(); + let taffy_node = context.node_layout.get(node); + let taffy_node = match taffy_node { + Some(taffy_node) => taffy_node, + None => { return None } + }; + let layout = *context.taffy.layout(*taffy_node).unwrap(); + + if layout.location.x <= location.x && layout.location.y <= location.y && layout.location.x + layout.size.width >= location.x && layout.location.y + layout.size.height >= location.y { + match children { + None => { + Some(vec![node.clone()]) + }, + Some(children) => { + let mut result = vec![node.clone()]; + for child in children { + if let Some(mut path) = get_element_at(child, context, location) { + result.append(&mut path); + } + } + Some(result) + } + } + } else { + None + } } pub fn layout_recursively(node: &SharedTNode, context: &mut RenderContext) -> taffy::node::Node {