Skip to content

Commit

Permalink
potential fix for SecondHalfGames#149 - remove clipping clip rect whe…
Browse files Browse the repository at this point in the history
…n pushing new clip rect, more sophisticated example for dropdown to test SecondHalfGames#149
  • Loading branch information
msparkles committed Apr 18, 2024
1 parent f11f4f4 commit 23c16b2
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 50 deletions.
2 changes: 1 addition & 1 deletion crates/yakui-core/src/input/input_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ fn hit_test(_dom: &Dom, layout: &LayoutDom, coords: Vec2, output: &mut Vec<Widge

let mut rect = layout_node.rect;
let mut node = layout_node;
while let Some(parent) = node.clipped_by {
if let Some(parent) = node.clipped_by {
node = layout.get(parent).unwrap();
rect = rect.constrain(node.rect);
}
Expand Down
43 changes: 35 additions & 8 deletions crates/yakui-core/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::collections::VecDeque;

use glam::Vec2;
use thunderdome::Arena;
use thunderdome::{Arena, Index};

use crate::dom::Dom;
use crate::event::EventInterest;
Expand All @@ -17,7 +17,8 @@ use crate::widget::LayoutContext;
#[derive(Debug)]
pub struct LayoutDom {
nodes: Arena<LayoutDomNode>,
clip_stack: Vec<WidgetId>,
clip_stack: Arena<Vec<WidgetId>>,
current_clip_stack: Option<Index>,

unscaled_viewport: Rect,
scale_factor: f32,
Expand All @@ -44,14 +45,18 @@ pub struct LayoutDomNode {

/// What events the widget reported interest in.
pub event_interest: EventInterest,

/// Which clip stack the widget belongs in.
pub clip_stack_id: Index,
}

impl LayoutDom {
/// Create an empty `LayoutDom`.
pub fn new() -> Self {
Self {
nodes: Arena::new(),
clip_stack: Vec::new(),
clip_stack: Arena::new(),
current_clip_stack: None,

unscaled_viewport: Rect::ONE,
scale_factor: 1.0,
Expand Down Expand Up @@ -120,6 +125,7 @@ impl LayoutDom {
log::debug!("LayoutDom::calculate_all()");

self.clip_stack.clear();
self.current_clip_stack = None;
self.interest_mouse.clear();

let constraints = Constraints::tight(self.viewport().size());
Expand All @@ -141,6 +147,7 @@ impl LayoutDom {
constraints: Constraints,
) -> Vec2 {
dom.enter(id);

let dom_node = dom.get(id).unwrap();

let context = LayoutContext {
Expand Down Expand Up @@ -168,16 +175,24 @@ impl LayoutDom {
self.interest_mouse.pop_layer();
}

if self.current_clip_stack.is_none() {
self.new_clip_stack(dom);
}

// There should always be a currently active clip stack.
let clip_stack_id = self.current_clip_stack.unwrap();
let clip_stack = self.clip_stack.get_mut(clip_stack_id).unwrap();

// If the widget called enable_clipping() during layout, it will be on
// top of the clip stack at this point.
let clipping_enabled = self.clip_stack.last() == Some(&id);
let clipping_enabled = clip_stack.last() == Some(&id);

// If this node enabled clipping, the next node under that is the node
// that clips this one.
let clipped_by = if clipping_enabled {
self.clip_stack.iter().nth_back(2).copied()
clip_stack.iter().nth_back(2).copied()
} else {
self.clip_stack.last().copied()
clip_stack.last().copied()
};

self.nodes.insert_at(
Expand All @@ -188,11 +203,12 @@ impl LayoutDom {
new_layer,
clipped_by,
event_interest,
clip_stack_id,
},
);

if clipping_enabled {
self.clip_stack.pop();
clip_stack.pop();
}

dom.exit(id);
Expand All @@ -201,7 +217,18 @@ impl LayoutDom {

/// Enables clipping for the currently active widget.
pub fn enable_clipping(&mut self, dom: &Dom) {
self.clip_stack.push(dom.current());
self.clip_stack
.get_mut(self.current_clip_stack.unwrap())
.unwrap()
.push(dom.current());
}

/// Create a new clip stack for the currently active widget.
pub fn new_clip_stack(&mut self, dom: &Dom) {
self.current_clip_stack = Some(dom.current().index());
let old = self.clip_stack.insert_at(dom.current().index(), Vec::new());
debug_assert!(old.is_none(), "clip_stack id clashed");
self.enable_clipping(dom);
}

/// Put this widget and its children into a new layer.
Expand Down
50 changes: 40 additions & 10 deletions crates/yakui-core/src/paint/paint_dom.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;

use glam::Vec2;
use thunderdome::Arena;
use thunderdome::{Arena, Index};

use crate::dom::Dom;
use crate::geometry::Rect;
Expand All @@ -24,7 +24,8 @@ pub struct PaintDom {
scale_factor: f32,

layers: PaintLayers,
clip_stack: Vec<Rect>,
clip_stack: Arena<Vec<Rect>>,
current_clip_id: Option<Index>,
}

impl PaintDom {
Expand All @@ -37,14 +38,16 @@ impl PaintDom {
unscaled_viewport: Rect::ONE,
scale_factor: 1.0,
layers: PaintLayers::new(),
clip_stack: Vec::new(),
clip_stack: Arena::new(),
current_clip_id: None,
}
}

/// Prepares the PaintDom to be updated for the frame.
pub fn start(&mut self) {
self.texture_edits.clear();
self.clip_stack.clear();
self.current_clip_id = None;
}

/// Returns the size of the surface that is being painted onto.
Expand Down Expand Up @@ -73,12 +76,25 @@ impl PaintDom {
profiling::scope!("PaintDom::paint");

let layout_node = layout.get(id).unwrap();
if layout_node.clipping_enabled {
self.push_clip(layout_node.rect);
}

if layout_node.new_layer {
self.layers.push();
}
if Some(layout_node.clip_stack_id) != self.current_clip_id {
println!(
"{:?}, {:?}",
self.current_clip_id, layout_node.clip_stack_id
);
self.current_clip_id = Some(layout_node.clip_stack_id);

if !self.clip_stack.contains(layout_node.clip_stack_id) {
self.clip_stack
.insert_at(layout_node.clip_stack_id, Vec::new());
}
}
if layout_node.clipping_enabled {
self.push_clip(layout_node.rect);
}

dom.enter(id);

Expand Down Expand Up @@ -184,7 +200,10 @@ impl PaintDom {
.current_mut()
.expect("an active layer is required to call add_mesh");

let current_clip = self.clip_stack.last().copied();
let current_clip_id = self.current_clip_id.unwrap();
let clip_stack = self.clip_stack.get(current_clip_id).unwrap();
let current_clip = clip_stack.last().copied();

let call = match layer.calls.last_mut() {
Some(PaintCall::Yakui(call))
if call.texture == texture_id
Expand Down Expand Up @@ -231,16 +250,27 @@ impl PaintDom {
region.size() * self.scale_factor,
);

if let Some(previous) = self.clip_stack.last() {
let current_clip_id = self.current_clip_id.unwrap();
let clip_stack = self.clip_stack.get_mut(current_clip_id).unwrap();

if let Some(previous) = clip_stack.last() {
println!("{previous:?}");
unscaled = unscaled.constrain(*previous);
}

self.clip_stack.push(unscaled);
clip_stack.push(unscaled);
}

/// Pop the most recent clip region, restoring the previous clipping rect.
fn pop_clip(&mut self) {
let top = self.clip_stack.pop();
let current_clip_id = self.current_clip_id.unwrap();

let top = {
let clip_stack = self.clip_stack.get_mut(current_clip_id).unwrap();

clip_stack.pop()
};

debug_assert!(
top.is_some(),
"cannot call pop_clip without a corresponding push_clip call"
Expand Down
1 change: 1 addition & 0 deletions crates/yakui-widgets/src/widgets/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ impl Widget for LayerWidget {

fn layout(&self, mut ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2 {
ctx.layout.new_layer(ctx.dom);
ctx.layout.new_clip_stack(ctx.dom);

let node = ctx.dom.get_current();
let mut size = Vec2::ZERO;
Expand Down
72 changes: 41 additions & 31 deletions crates/yakui/examples/dropdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,61 @@
#![allow(clippy::collapsible_if)]

use yakui::widgets::Layer;
use yakui::{align, button, column, reflow, use_state, widgets::Pad, Alignment, Dim2};
use yakui::widgets::{Layer, Scrollable};
use yakui::{button, column, reflow, use_state, Alignment, Dim2};
use yakui::{constrained, row, Constraints, Vec2};
use yakui_core::Pivot;

pub fn run() {
let open = use_state(|| false);
let options = ["Hello", "World", "Foobar"];
let options = ["Hello", "World", "Foobar", "Meow", "Woof"];
let selected = use_state(|| 0);

align(Alignment::TOP_LEFT, || {
column(|| {
if button("Upper Button").clicked {
println!("Upper button clicked");
}
row(|| {
constrained(Constraints::loose(Vec2::new(f32::INFINITY, 50.0)), || {
Scrollable::vertical().show(|| {
column(|| {
if button("Upper Button").clicked {
println!("Upper button clicked");
}

column(|| {
if button(options[selected.get()]).clicked {
open.modify(|x| !x);
}
column(|| {
if button(options[selected.get()]).clicked {
open.modify(|x| !x);
}

if open.get() {
Pad::ZERO.show(|| {
Layer::new().show(|| {
if open.get() {
reflow(Alignment::BOTTOM_LEFT, Pivot::TOP_LEFT, Dim2::ZERO, || {
column(|| {
let current = selected.get();
for (i, option) in options.iter().enumerate() {
if i != current {
if button(*option).clicked {
selected.set(i);
open.set(false);
}
}
}
Layer::new().show(|| {
constrained(
Constraints::loose(Vec2::new(f32::INFINITY, 80.0)),
|| {
Scrollable::vertical().show(|| {
column(|| {
let current = selected.get();
for (i, option) in options.iter().enumerate() {
if i != current {
if button(*option).clicked {
selected.set(i);
open.set(false);
}
}
}
});
});
},
);
});
});
});
}
});
}
});
});

if button("Lower Button").clicked {
println!("Lower button clicked");
}
});

if button("Side Button").clicked {
println!("Side button clicked");
}
});
}

Expand Down

0 comments on commit 23c16b2

Please sign in to comment.