Skip to content

Commit

Permalink
feat: scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
ten3roberts committed Mar 25, 2024
1 parent bbd1b1d commit 7bb14fd
Show file tree
Hide file tree
Showing 23 changed files with 591 additions and 253 deletions.
1 change: 0 additions & 1 deletion examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,4 @@ fn app() -> impl Widget {
})),
))),
))
.contain_margins(true)
}
37 changes: 37 additions & 0 deletions examples/offset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use tracing_subscriber::{
prelude::__tracing_subscriber_SubscriberExt, registry, util::SubscriberInitExt, EnvFilter,
};
use tracing_tree::HierarchicalLayer;
use violet::core::{
style::{colors::EMERALD_500, secondary_background, spacing_medium, Background, SizeExt},
unit::Unit,
widget::{Positioned, Rectangle, Stack},
Widget,
};

pub fn main() -> anyhow::Result<()> {
registry()
.with(
HierarchicalLayer::default()
.with_deferred_spans(true)
.with_span_retrace(true)
.with_indent_lines(true),
)
.with(EnvFilter::from_default_env())
.init();

violet_wgpu::AppBuilder::new().run(app())
}

fn app() -> impl Widget {
Stack::new(
Positioned::new(
Rectangle::new(EMERALD_500)
.with_min_size(Unit::px2(100.0, 100.0))
.with_margin(spacing_medium()),
)
.with_offset(Unit::px2(10.0, 10.0)),
)
.with_padding(spacing_medium())
.with_background(Background::new(secondary_background()))
}
85 changes: 85 additions & 0 deletions examples/scroll.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use futures::StreamExt;
use futures_signals::signal::Mutable;
use glam::{vec2, Vec2};
use itertools::Itertools;
use palette::{FromColor, Hsl, Hsv, IntoColor, Oklcha, Srgba};
use tracing_subscriber::{
prelude::__tracing_subscriber_SubscriberExt, registry, util::SubscriberInitExt, EnvFilter,
};
use tracing_tree::HierarchicalLayer;
use violet::core::{
style::{spacing_small, SizeExt},
unit::Unit,
widget::{col, Rectangle},
Widget,
};
use violet_core::{
state::{State, StateStream},
style::{colors::AMBER_500, secondary_background, spacing_medium, Background},
widget::{label, Button, Checkbox, Scroll, StreamWidget},
};

pub fn main() -> anyhow::Result<()> {
registry()
.with(
HierarchicalLayer::default()
.with_deferred_spans(true)
.with_span_retrace(true)
.with_indent_lines(true),
)
.with(EnvFilter::from_default_env())
.init();

violet_wgpu::AppBuilder::new().run(app())
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ColorSpace {
Oklcha,
Hsv,
Hsl,
}

fn app() -> impl Widget {
let color_space = Mutable::new(ColorSpace::Oklcha);
col((
Checkbox::label(
"Oklch",
color_space.clone().map(
|v| v == ColorSpace::Oklcha,
|v| {
if v {
ColorSpace::Oklcha
} else {
ColorSpace::Hsv
}
},
),
),
Scroll::new(
StreamWidget(color_space.stream().map(|color_space| {
col((0..180)
.map(|v| {
let hue = v as f32 * 2.0;

let color: Srgba = match color_space {
ColorSpace::Oklcha => Oklcha::new(0.5, 0.37, hue, 1.0).into_color(),
ColorSpace::Hsv => Hsv::new(hue, 1.0, 1.0).into_color(),
ColorSpace::Hsl => Hsl::new(hue, 1.0, 0.5).into_color(),
};

Rectangle::new(color)
// .with_margin(spacing_medium())
.with_min_size(Unit::px2(100.0, 20.0))
.with_maximize(Vec2::X)
})
.collect_vec())
.with_padding(spacing_medium())
})),
// .with_background(Background::new(violet_core::style::accent_background()))
),
Button::label("Button"),
))
.with_background(Background::new(secondary_background()))
.with_padding(spacing_medium())
}
23 changes: 15 additions & 8 deletions violet-core/src/components.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::time::Duration;

use flax::{component, Debuggable, Entity, EntityRef, Exclusive};
use glam::Vec2;
use glam::{Mat4, Vec2};
use image::DynamicImage;
use palette::Srgba;

Expand All @@ -21,18 +21,25 @@ component! {

/// Defines the outer bounds of a widget relative to its position
pub rect: Rect => [ Debuggable ],
pub screen_rect: Rect => [ Debuggable ],
/// Clips rendering to the bounds of the widget, relative to the widget itself
pub clip_mask: Rect => [ Debuggable ],

/// Position relative to parent
/// The merged clip mask of the widget and its parents
pub screen_clip_mask: Rect => [ Debuggable ],

/// Position relative to parent for layout position.
pub local_position: Vec2 => [ Debuggable ],

/// Specifies in screen space where the widget rect upper left corner is
pub screen_position: Vec2 => [ Debuggable ],
/// Offset the widget from its original position.
///
/// This influences the layout bounds and the final position of the widget, and will move other
/// widgets around in flow layouts.
pub offset: Unit<Vec2> => [ Debuggable ],

pub rotation: f32 => [ Debuggable ],
/// Optional transform of the widget. Applied after layout
pub transform: Mat4,

/// Offset the widget from its original position
pub offset: Unit<Vec2> => [ Debuggable ],
pub screen_transform: Mat4,

/// Explicit widget size. This will override the intrinsic size of the widget.
///
Expand Down
36 changes: 36 additions & 0 deletions violet-core/src/hierarchy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use flax::{EntityRef, World};

use crate::components::children;

pub struct OrderedDfsIterator<'a> {
world: &'a World,
// queue: VecDeque<EntityRef<'a>>,
stack: Vec<EntityRef<'a>>,
}

impl<'a> OrderedDfsIterator<'a> {
pub fn new(world: &'a World, stack: EntityRef<'a>) -> Self {
Self {
world,
stack: vec![stack],
}
}
}

impl<'a> Iterator for OrderedDfsIterator<'a> {
type Item = EntityRef<'a>;

fn next(&mut self) -> Option<Self::Item> {
let entity = self.stack.pop()?;
if let Ok(children) = entity.get(children()) {
self.stack.extend(
children
.iter()
.rev()
.map(|&id| self.world.entity(id).unwrap()),
);
}

Some(entity)
}
}
99 changes: 53 additions & 46 deletions violet-core/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use flax::{
component, components::child_of, entity_ids, fetch::Satisfied, filter::All, Component, Entity,
EntityIds, EntityRef, Fetch, FetchExt, Query, Topo, World,
};
use glam::Vec2;
use flax::{component, Entity, EntityRef, FetchExt, World};
use glam::{Vec2, Vec3Swizzles};

/// NOTE: maybe redefine these types ourselves
pub use winit::{event, keyboard};
Expand All @@ -12,73 +9,58 @@ use winit::{
};

use crate::{
components::{rect, screen_position, screen_rect},
components::{rect, screen_transform},
hierarchy::OrderedDfsIterator,
scope::ScopeRef,
Frame, Rect,
Frame,
};

pub struct Input {}

#[derive(Fetch)]
struct IntersectQuery {
id: EntityIds,
rect: Component<Rect>,
screen_pos: Component<Vec2>,
sticky: Satisfied<Component<()>>,
focusable: Component<()>,
}

impl IntersectQuery {
pub fn new() -> Self {
Self {
id: entity_ids(),
rect: rect(),
screen_pos: screen_position(),
sticky: focus_sticky().satisfied(),
focusable: focusable(),
}
}
}

#[derive(Debug, Clone)]
struct FocusedEntity {
id: Entity,
sticky: bool,
}

pub struct InputState {
root: Entity,
focused: Option<FocusedEntity>,
pos: Vec2,
intersect_query: Query<IntersectQuery, All, Topo>,
modifiers: ModifiersState,
}

impl InputState {
pub fn new(pos: Vec2) -> Self {
pub fn new(root: Entity, pos: Vec2) -> Self {
Self {
focused: None,
pos,
intersect_query: Query::new(IntersectQuery::new()).topo(child_of),
modifiers: Default::default(),
root,
}
}

pub fn on_mouse_input(&mut self, frame: &mut Frame, state: ElementState, button: MouseButton) {
let cursor_pos = self.pos;
fn find_intersect(&self, frame: &Frame, pos: Vec2) -> Option<(Entity, Vec2)> {
let query = (screen_transform(), rect()).filtered(focusable().with());
OrderedDfsIterator::new(&frame.world, frame.world.entity(self.root).unwrap())
.filter_map(|entity| {
let mut query = entity.query(&query);
let (transform, rect) = query.get()?;

let intersect = self
.intersect_query
.borrow(frame.world())
.iter()
.filter_map(|item| {
let local_pos = cursor_pos - *item.screen_pos;
if item.rect.contains_point(local_pos) {
Some((item.id, (*item.screen_pos + item.rect.min)))
let local_pos = transform.inverse().transform_point3(pos.extend(0.0)).xy();

if rect.contains_point(local_pos) {
Some((entity.id(), local_pos))
} else {
None
}
})
.last();
.last()
}

pub fn on_mouse_input(&mut self, frame: &mut Frame, state: ElementState, button: MouseButton) {
let cursor_pos = self.pos;
let intersect = self.find_intersect(frame, cursor_pos);

match (state, &self.focused, intersect) {
// Focus changed
Expand All @@ -94,13 +76,13 @@ impl InputState {

// Send the event to the intersected entity

if let Some((id, origin)) = intersect {
if let Some((id, local_pos)) = intersect {
let entity = frame.world().entity(id).unwrap();

let cursor = CursorMove {
modifiers: self.modifiers,
absolute_pos: self.pos,
local_pos: self.pos - origin,
local_pos,
};
if let Ok(mut on_input) = entity.get_mut(on_mouse_input()) {
let s = ScopeRef::new(frame, entity);
Expand All @@ -121,15 +103,33 @@ impl InputState {
self.pos = pos;

if let Some(entity) = &self.focused(&frame.world) {
let screen_rect = entity.get_copy(screen_rect()).unwrap_or_default();
let transform = entity.get_copy(screen_transform()).unwrap_or_default();
if let Ok(mut on_input) = entity.get_mut(on_cursor_move()) {
let s = ScopeRef::new(frame, *entity);
on_input(
&s,
CursorMove {
modifiers: self.modifiers,
absolute_pos: pos,
local_pos: pos - screen_rect.min,
local_pos: transform.inverse().transform_point3(pos.extend(0.0)).xy(),
},
);
}
}
}

pub fn on_scroll(&mut self, frame: &mut Frame, delta: Vec2) {
let intersect = self.find_intersect(frame, self.pos);

if let Some((id, _)) = intersect {
let entity = frame.world().entity(id).unwrap();
if let Ok(mut on_input) = entity.get_mut(on_scroll()) {
let s = ScopeRef::new(frame, entity);
on_input(
&s,
Scroll {
delta,
modifiers: self.modifiers,
},
);
}
Expand Down Expand Up @@ -206,6 +206,12 @@ pub struct CursorMove {
pub local_pos: Vec2,
}

#[derive(Debug, Clone)]
pub struct Scroll {
pub delta: Vec2,
pub modifiers: ModifiersState,
}

pub struct KeyboardInput {
pub modifiers: ModifiersState,

Expand All @@ -221,4 +227,5 @@ component! {
pub on_cursor_move: InputEventHandler<CursorMove>,
pub on_mouse_input: InputEventHandler<MouseInput>,
pub on_keyboard_input: InputEventHandler<KeyboardInput>,
pub on_scroll: InputEventHandler<Scroll>,
}
Loading

0 comments on commit 7bb14fd

Please sign in to comment.