BROKEN: tonights progress

This commit is contained in:
Daniel Bulant 2023-10-23 23:05:15 +02:00
parent b84a8fba38
commit 5d53e10a32
5 changed files with 379 additions and 17 deletions

View file

@ -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::<CurrentRenderer>::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<CurrentRenderer> = Arc::new(RwLock::new(root));
let groot: SharedNode = Arc::new(RwLock::new(root));
mangui::run_event_loop(groot);
}

231
ui/src/events/mod.rs Normal file
View file

@ -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<SharedNode>,
/// 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<VirtualKeyCode>,
/// 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<MouseButton>,
/// 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<Point<f32>> for Location {
fn from(point: Point<f32>) -> 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<Size<Dimension>> for Location {
fn into(self) -> Size<Dimension> {
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
}
}
}

View file

@ -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<Vec<WeakNode>> = None;
let mut mouse_values: HashMap<DeviceId, MouseValue> = 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<Vec<SharedNode>> = 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<T>(vec: Vec<Option<T>>) -> Option<Vec<T>> {
vec.into_iter().collect::<Option<Vec<T>>>()
}
fn create_window(event_loop: &EventLoop<()>) -> (PossiblyCurrentContext, Display, Window, Surface<WindowSurface>) {
let window_builder = WindowBuilder::new()
.with_inner_size(PhysicalSize::new(1000., 600.))

View file

@ -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
}
}
}

View file

@ -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<RwLock<dyn Node>>;
@ -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<RwLock<Node>>]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<Vec<SharedTNode>> {
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 {