sense specific buttons

This commit is contained in:
2025-09-15 22:22:52 -04:00
parent 21aa2b3501
commit b48acccb8d
8 changed files with 167 additions and 107 deletions

View File

@@ -1,17 +1,17 @@
use crate::prelude::*; use crate::prelude::*;
pub trait Sensable<W, Ctx, Tag> { pub trait Sensable<W, Ctx, Tag> {
fn on(self, sense: Senses, f: impl SenseFn<Ctx>) -> impl WidgetIdFn<W, Ctx>; fn on(self, sense: impl Into<Senses>, f: impl SenseFn<Ctx>) -> impl WidgetIdFn<W, Ctx>;
fn id_on( fn id_on(
self, self,
senses: Senses, senses: impl Into<Senses>,
f: impl FnMut(&WidgetId<W>, &mut Ctx, SenseCtx) + 'static, f: impl FnMut(&WidgetId<W>, &mut Ctx, SenseCtx) + 'static,
) -> impl WidgetIdFn<W, Ctx> ) -> impl WidgetIdFn<W, Ctx>
where where
W: Widget; W: Widget;
fn edit_on( fn edit_on(
self, self,
senses: Senses, senses: impl Into<Senses>,
f: impl FnMut(&mut W, SenseCtx) + 'static, f: impl FnMut(&mut W, SenseCtx) + 'static,
) -> impl WidgetIdFn<W, Ctx> ) -> impl WidgetIdFn<W, Ctx>
where where
@@ -20,13 +20,17 @@ pub trait Sensable<W, Ctx, Tag> {
} }
impl<W: WidgetLike<Ctx, Tag>, Ctx, Tag> Sensable<W::Widget, Ctx, Tag> for W { impl<W: WidgetLike<Ctx, Tag>, Ctx, Tag> Sensable<W::Widget, Ctx, Tag> for W {
fn on(self, senses: Senses, f: impl SenseFn<Ctx>) -> impl WidgetIdFn<W::Widget, Ctx> { fn on(
self,
senses: impl Into<Senses>,
f: impl SenseFn<Ctx>,
) -> impl WidgetIdFn<W::Widget, Ctx> {
move |ui| { move |ui| {
let id = self.add(ui); let id = self.add(ui);
ui.add_sensor( ui.add_sensor(
&id, &id,
Sensor { Sensor {
senses, senses: senses.into(),
f: Box::new(f), f: Box::new(f),
}, },
); );
@@ -35,7 +39,7 @@ impl<W: WidgetLike<Ctx, Tag>, Ctx, Tag> Sensable<W::Widget, Ctx, Tag> for W {
} }
fn id_on( fn id_on(
self, self,
senses: Senses, senses: impl Into<Senses>,
mut f: impl FnMut(&WidgetId<W::Widget>, &mut Ctx, SenseCtx) + 'static, mut f: impl FnMut(&WidgetId<W::Widget>, &mut Ctx, SenseCtx) + 'static,
) -> impl WidgetIdFn<W::Widget, Ctx> ) -> impl WidgetIdFn<W::Widget, Ctx>
where where
@@ -48,7 +52,7 @@ impl<W: WidgetLike<Ctx, Tag>, Ctx, Tag> Sensable<W::Widget, Ctx, Tag> for W {
} }
fn edit_on( fn edit_on(
self, self,
senses: Senses, senses: impl Into<Senses>,
mut f: impl FnMut(&mut W::Widget, SenseCtx) + 'static, mut f: impl FnMut(&mut W::Widget, SenseCtx) + 'static,
) -> impl WidgetIdFn<W::Widget, Ctx> ) -> impl WidgetIdFn<W::Widget, Ctx>
where where

View File

@@ -123,7 +123,20 @@ impl<'a> TextEditCtx<'a> {
self.text.cursor = Some(cursor); self.text.cursor = Some(cursor);
} }
} }
pub fn insert(&mut self, text: &str) { pub fn insert(&mut self, text: &str) {
let mut lines = text.split('\n');
let Some(first) = lines.next() else {
return;
};
self.insert_inner(first);
for line in lines {
self.newline();
self.insert_inner(line);
}
}
fn insert_inner(&mut self, text: &str) {
if let Some(cursor) = &mut self.text.cursor { if let Some(cursor) = &mut self.text.cursor {
let line = &mut self.text.buf.lines[cursor.line]; let line = &mut self.text.buf.lines[cursor.line];
let mut line_text = line.text().to_string(); let mut line_text = line.text().to_string();
@@ -189,7 +202,7 @@ impl<'a> TextEditCtx<'a> {
NamedKey::Backspace => self.backspace(), NamedKey::Backspace => self.backspace(),
NamedKey::Delete => self.delete(), NamedKey::Delete => self.delete(),
NamedKey::Space => self.insert(" "), NamedKey::Space => self.insert(" "),
NamedKey::Enter => self.newline(), NamedKey::Enter => self.insert("\n"),
NamedKey::ArrowRight => self.motion(Motion::Right), NamedKey::ArrowRight => self.motion(Motion::Right),
NamedKey::ArrowLeft => self.motion(Motion::Left), NamedKey::ArrowLeft => self.motion(Motion::Left),
NamedKey::ArrowUp => self.motion(Motion::Up), NamedKey::ArrowUp => self.motion(Motion::Up),

View File

@@ -1,21 +1,68 @@
use std::ops::{BitOr, Deref, DerefMut};
use crate::{ use crate::{
layout::{Ui, UiRegion, Vec2}, layout::{Ui, UiRegion, Vec2},
util::{HashMap, Id, IdVec, bitflags}, util::{HashMap, Id, IdVec},
}; };
bitflags!(Sense, Senses, senses { pub trait CursorCtx {
1 << 0; PressStart, PRESS_START, fn cursor_state(&self) -> &CursorState;
1 << 1; Pressing, PRESSING, }
1 << 2; PressEnd, PRESS_END,
1 << 3; HoverStart, HOVER_START,
1 << 4; Hovering, HOVERING,
1 << 5; HoverEnd, HOVER_END,
});
pub enum Button {
Left,
Right,
Middle,
}
pub enum Sense {
PressStart(Button),
Pressing(Button),
PressEnd(Button),
HoverStart,
Hovering,
HoverEnd,
}
pub struct Senses(Vec<Sense>);
impl Sense {
pub fn click() -> Self {
Self::PressStart(Button::Left)
}
pub fn unclick() -> Self {
Self::PressEnd(Button::Left)
}
}
#[derive(Default, Clone)]
pub struct CursorState { pub struct CursorState {
pub pos: Vec2, pub pos: Vec2,
pub exists: bool, pub exists: bool,
pub pressed: bool, pub buttons: CursorButtons,
}
#[derive(Default, Clone)]
pub struct CursorButtons {
pub left: ActivationState,
pub middle: ActivationState,
pub right: ActivationState,
}
impl CursorButtons {
pub fn select(&self, button: &Button) -> &ActivationState {
match button {
Button::Left => &self.left,
Button::Right => &self.right,
Button::Middle => &self.middle,
}
}
pub fn end_frame(&mut self) {
self.left.end_frame();
self.middle.end_frame();
self.right.end_frame();
}
} }
#[derive(Debug, Clone, Copy, Default, PartialEq)] #[derive(Debug, Clone, Copy, Default, PartialEq)]
@@ -37,7 +84,6 @@ pub type ActiveSensors = IdVec<SenseShape>;
pub type SenseShape = UiRegion; pub type SenseShape = UiRegion;
pub struct SensorGroup<Ctx> { pub struct SensorGroup<Ctx> {
pub hover: ActivationState, pub hover: ActivationState,
pub cursor: ActivationState,
pub sensors: Vec<Sensor<Ctx>>, pub sensors: Vec<Sensor<Ctx>>,
} }
@@ -63,11 +109,13 @@ pub fn run_sensors<Ctx: UiCtx>(ctx: &mut Ctx, cursor: &CursorState, window_size:
let region = shape.to_screen(window_size); let region = shape.to_screen(window_size);
let in_shape = cursor.exists && region.contains(cursor.pos); let in_shape = cursor.exists && region.contains(cursor.pos);
group.hover.update(in_shape); group.hover.update(in_shape);
group.cursor.update(cursor.pressed && in_shape); if group.hover == ActivationState::Off {
continue;
}
let mut ran = false; let mut ran = false;
for sensor in &mut group.sensors { for sensor in &mut group.sensors {
if should_run(sensor.senses, group.cursor, group.hover) { if should_run(&sensor.senses, &cursor.buttons, group.hover) {
ran = true; ran = true;
let sctx = SenseCtx { let sctx = SenseCtx {
cursor: cursor.pos - region.top_left, cursor: cursor.pos - region.top_left,
@@ -84,12 +132,12 @@ pub fn run_sensors<Ctx: UiCtx>(ctx: &mut Ctx, cursor: &CursorState, window_size:
ctx.ui().active.sensors = active; ctx.ui().active.sensors = active;
} }
pub fn should_run(senses: Senses, cursor: ActivationState, hover: ActivationState) -> bool { pub fn should_run(senses: &Senses, cursor: &CursorButtons, hover: ActivationState) -> bool {
for sense in senses.iter() { for sense in senses.iter() {
if match sense { if match sense {
Sense::PressStart => cursor.is_start(), Sense::PressStart(button) => cursor.select(button).is_start(),
Sense::Pressing => cursor.is_on(), Sense::Pressing(button) => cursor.select(button).is_on(),
Sense::PressEnd => cursor.is_end(), Sense::PressEnd(button) => cursor.select(button).is_end(),
Sense::HoverStart => hover.is_start(), Sense::HoverStart => hover.is_start(),
Sense::Hovering => hover.is_on(), Sense::Hovering => hover.is_on(),
Sense::HoverEnd => hover.is_end(), Sense::HoverEnd => hover.is_end(),
@@ -133,14 +181,58 @@ impl ActivationState {
}, },
} }
} }
pub fn end_frame(&mut self) {
match self {
Self::Start => *self = Self::On,
Self::End => *self = Self::Off,
_ => (),
}
}
} }
impl<Ctx> Default for SensorGroup<Ctx> { impl<Ctx> Default for SensorGroup<Ctx> {
fn default() -> Self { fn default() -> Self {
Self { Self {
hover: Default::default(), hover: Default::default(),
cursor: Default::default(),
sensors: Default::default(), sensors: Default::default(),
} }
} }
} }
impl Deref for Senses {
type Target = Vec<Sense>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Senses {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Sense> for Senses {
fn from(val: Sense) -> Self {
Senses(vec![val])
}
}
impl BitOr for Sense {
type Output = Senses;
fn bitor(self, rhs: Self) -> Self::Output {
Senses(vec![self, rhs])
}
}
impl BitOr<Sense> for Senses {
type Output = Self;
fn bitor(mut self, rhs: Sense) -> Self::Output {
self.0.push(rhs);
self
}
}

View File

@@ -1,5 +1,5 @@
const RECT: u32 = 0; const RECT: u32 = 0u;
const TEXTURE: u32 = 1; const TEXTURE: u32 = 1u;
@group(0) @binding(0) @group(0) @binding(0)
var<uniform> window: WindowUniform; var<uniform> window: WindowUniform;

View File

@@ -1,31 +1,40 @@
use ui::layout::{CursorState, Vec2}; use ui::layout::{CursorState, Vec2};
use winit::event::WindowEvent; use winit::event::{MouseButton, WindowEvent};
use crate::testing::Client; use crate::testing::Client;
#[derive(Default)] #[derive(Default)]
pub struct Input { pub struct Input {
mouse_pos: Vec2, cursor: CursorState,
mouse_pressed: bool,
mouse_exists: bool,
} }
impl Input { impl Input {
pub fn event(&mut self, event: &WindowEvent) { pub fn event(&mut self, event: &WindowEvent) {
match event { match event {
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
self.mouse_pos = Vec2::new(position.x as f32, position.y as f32); self.cursor.pos = Vec2::new(position.x as f32, position.y as f32);
self.mouse_exists = true; self.cursor.exists = true;
} }
WindowEvent::MouseInput { state, button, .. } => match button { WindowEvent::MouseInput { state, button, .. } => {
winit::event::MouseButton::Left => { let buttons = &mut self.cursor.buttons;
self.mouse_pressed = state.is_pressed(); let pressed = state.is_pressed();
} match button {
_ => (), MouseButton::Left => buttons.left.update(pressed),
}, MouseButton::Right => buttons.right.update(pressed),
MouseButton::Middle => buttons.middle.update(pressed),
_ => (), _ => (),
} }
} }
WindowEvent::CursorLeft { .. } => {
self.cursor.exists = false;
}
_ => (),
}
}
pub fn end_frame(&mut self) {
self.cursor.buttons.end_frame();
}
} }
impl Client { impl Client {
@@ -34,11 +43,7 @@ impl Client {
(size.width, size.height).into() (size.width, size.height).into()
} }
pub fn cursor_state(&self) -> CursorState { pub fn cursor_state(&self) -> &CursorState {
CursorState { &self.input.cursor
pos: self.input.mouse_pos,
exists: self.input.mouse_exists,
pressed: self.input.mouse_pressed,
}
} }
} }

View File

@@ -3,7 +3,6 @@ use std::sync::Arc;
use app::App; use app::App;
use cosmic_text::Family; use cosmic_text::Family;
use render::Renderer; use render::Renderer;
use senses::*;
use ui::prelude::*; use ui::prelude::*;
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
@@ -73,7 +72,7 @@ impl Client {
let add_button = rect(Color::LIME) let add_button = rect(Color::LIME)
.radius(30) .radius(30)
.on(PRESS_START, move |ctx: &mut Client, _| { .on(Sense::click(), move |ctx: &mut Client, _| {
let child = ctx let child = ctx
.ui .ui
.add(image(include_bytes!("assets/sungals.png")).center()) .add(image(include_bytes!("assets/sungals.png")).center())
@@ -85,7 +84,7 @@ impl Client {
let del_button = rect(Color::RED) let del_button = rect(Color::RED)
.radius(30) .radius(30)
.on(PRESS_START, move |ctx: &mut Client, _| { .on(Sense::click(), move |ctx: &mut Client, _| {
ctx.ui[span_add].children.pop(); ctx.ui[span_add].children.pop();
}) })
.size(150) .size(150)
@@ -124,7 +123,7 @@ impl Client {
texts, texts,
text_edit("add") text_edit("add")
.font_size(30) .font_size(30)
.id_on(PRESS_START, |id, client: &mut Client, ctx| { .id_on(Sense::click(), |id, client: &mut Client, ctx| {
client.ui.text(id).select(ctx.cursor, ctx.size); client.ui.text(id).select(ctx.cursor, ctx.size);
client.selected = Some(id.clone()); client.selected = Some(id.clone());
}) })
@@ -135,14 +134,14 @@ impl Client {
let switch_button = |color, to, label| { let switch_button = |color, to, label| {
let rect = rect(color) let rect = rect(color)
.id_on(PRESS_START, move |id, ctx: &mut Client, _| { .id_on(Sense::click(), move |id, ctx: &mut Client, _| {
ctx.ui[main].inner.set_static(to); ctx.ui[main].inner.set_static(to);
ctx.ui[id].color = color.add_rgb(-0.2); ctx.ui[id].color = color.add_rgb(-0.2);
}) })
.edit_on(HOVER_START | PRESS_END, move |r, _| { .edit_on(Sense::HoverStart | Sense::unclick(), move |r, _| {
r.color = color.add_rgb(0.4); r.color = color.add_rgb(0.4);
}) })
.edit_on(HOVER_END, move |r, _| { .edit_on(Sense::HoverEnd, move |r, _| {
r.color = color; r.color = color;
}); });
(rect, text(label).font_size(30)).stack() (rect, text(label).font_size(30)).stack()
@@ -185,7 +184,7 @@ impl Client {
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
self.input.event(&event); self.input.event(&event);
let cursor_state = self.cursor_state(); let cursor_state = self.cursor_state().clone();
let window_size = self.window_size(); let window_size = self.window_size();
run_sensors(self, &cursor_state, window_size); run_sensors(self, &cursor_state, window_size);
match event { match event {
@@ -221,6 +220,7 @@ impl Client {
if self.ui.needs_redraw() { if self.ui.needs_redraw() {
self.renderer.window().request_redraw(); self.renderer.window().request_redraw();
} }
self.input.end_frame();
} }
} }

View File

@@ -1,52 +0,0 @@
macro_rules! bitflags {
($enum:ident, $struct:ident, $mod:ident {$($val:expr; $ename:ident, $sname:ident,)*}) => {
#[repr(u32)]
#[derive(Clone, Copy, PartialEq)]
pub enum $enum {
$($ename = $val,)*
}
#[derive(Clone, Copy, PartialEq)]
pub struct $struct(u32);
#[allow(non_upper_case_globals)]
impl $struct {
$(pub const $sname: Self = Self($enum::$ename as u32);)*
pub fn iter(&self) -> impl Iterator<Item = $enum> {
$crate::util::Biterator::new(self.0).map(|v| unsafe {std::mem::transmute(v)})
}
}
impl std::ops::BitOr for $struct {
type Output = Self;
fn bitor(self, rhs: $struct) -> Self {
Self(self.0 | rhs.0)
}
}
pub mod $mod {
use super::*;
$(pub const $sname: $struct = $struct::$sname;)*
}
};
}
pub(crate) use bitflags;
pub struct Biterator(u32);
impl Iterator for Biterator {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.0 == 0 {
return None;
}
let val = 1 << self.0.trailing_zeros();
self.0 &= !val;
Some(val)
}
}
impl Biterator {
pub fn new(val: u32) -> Self {
Self(val)
}
}

View File

@@ -1,11 +1,9 @@
mod bitflags;
mod borrow; mod borrow;
mod id; mod id;
mod idvec; mod idvec;
mod math; mod math;
mod refcount; mod refcount;
pub(crate) use bitflags::*;
pub(crate) use borrow::*; pub(crate) use borrow::*;
pub(crate) use id::*; pub(crate) use id::*;
pub(crate) use idvec::*; pub(crate) use idvec::*;