mirror of
https://github.com/danbulant/mangui
synced 2026-06-20 23:01:11 +00:00
BROKEN: tonights progress
This commit is contained in:
parent
b84a8fba38
commit
5d53e10a32
5 changed files with 379 additions and 17 deletions
|
|
@ -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
231
ui/src/events/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
108
ui/src/lib.rs
108
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<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.))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue