Skip to content

Commit

Permalink
Add rounding to scale and Ui rects
Browse files Browse the repository at this point in the history
Helps avoiding disastrous rounding errors
  • Loading branch information
zakarumych committed Jan 27, 2025
1 parent ba6a91c commit 604c385
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 54 deletions.
76 changes: 40 additions & 36 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ impl<T> Snarl<T> {

let mut bg_r = ui.allocate_rect(ui.max_rect(), Sense::click_and_drag());
let viewport = bg_r.rect;
ui.set_clip_rect(viewport);
ui.set_clip_rect(rect_round(viewport));

let pivot = input.hover_pos.unwrap_or_else(|| viewport.center());

Expand Down Expand Up @@ -713,11 +713,12 @@ impl<T> Snarl<T> {
if viewport.contains(hover_pos) && ui.rect_contains_pointer(viewport) =>
{
if input.zoom_delta != 1.0 {
let new_scale = (snarl_state.scale()
* input.zoom_delta.powf(style.get_scale_velocity()))
.clamp(style.get_min_scale(), style.get_max_scale());

snarl_state.set_scale(new_scale);
snarl_state.zoom_delta(
input.zoom_delta,
style.get_scale_velocity(),
style.get_min_scale(),
style.get_max_scale(),
);
}
}
_ => {}
Expand Down Expand Up @@ -1127,12 +1128,12 @@ impl<T> Snarl<T> {
// Input pins on the left.
let inputs_ui = &mut ui.new_child(
UiBuilder::new()
.max_rect(inputs_rect)
.max_rect(rect_round(inputs_rect))
.layout(Layout::top_down(Align::Min))
.id_salt("inputs"),
);

inputs_ui.set_clip_rect(clip_rect.intersect(viewport));
inputs_ui.set_clip_rect(rect_round(clip_rect.intersect(viewport)));

for in_pin in inputs {
// Show input pin.
Expand Down Expand Up @@ -1160,7 +1161,7 @@ impl<T> Snarl<T> {
let pin_pos = pos2(input_x, y);

// Interact with pin shape.
ui.set_clip_rect(viewport);
ui.set_clip_rect(rect_round(viewport));

let r = ui.interact(
Rect::from_center_size(pin_pos, vec2(pin_size, pin_size)),
Expand Down Expand Up @@ -1215,7 +1216,7 @@ impl<T> Snarl<T> {
}

let mut pin_painter = ui.painter().clone();
pin_painter.set_clip_rect(viewport);
pin_painter.set_clip_rect(rect_round(viewport));

let wire_info = snarl_pin.draw(
snarl_state.scale(),
Expand Down Expand Up @@ -1273,12 +1274,12 @@ impl<T> Snarl<T> {

let outputs_ui = &mut ui.new_child(
UiBuilder::new()
.max_rect(outputs_rect)
.max_rect(rect_round(outputs_rect))
.layout(Layout::top_down(Align::Max))
.id_salt("outputs"),
);

outputs_ui.set_clip_rect(clip_rect.intersect(viewport));
outputs_ui.set_clip_rect(rect_round(clip_rect.intersect(viewport)));

// Output pins on the right.
for out_pin in outputs {
Expand Down Expand Up @@ -1307,7 +1308,7 @@ impl<T> Snarl<T> {

let pin_pos = pos2(output_x, y);

ui.set_clip_rect(viewport);
ui.set_clip_rect(rect_round(viewport));

let r = ui.interact(
Rect::from_center_size(pin_pos, vec2(pin_size, pin_size)),
Expand Down Expand Up @@ -1362,7 +1363,7 @@ impl<T> Snarl<T> {
}

let mut pin_painter = ui.painter().clone();
pin_painter.set_clip_rect(viewport);
pin_painter.set_clip_rect(rect_round(viewport));

let wire_info = snarl_pin.draw(
snarl_state.scale(),
Expand Down Expand Up @@ -1410,11 +1411,11 @@ impl<T> Snarl<T> {
{
let mut body_ui = ui.new_child(
UiBuilder::new()
.max_rect(body_rect)
.max_rect(rect_round(body_rect))
.layout(Layout::left_to_right(Align::Min))
.id_salt("body"),
);
body_ui.set_clip_rect(clip_rect.intersect(viewport));
body_ui.set_clip_rect(rect_round(clip_rect.intersect(viewport)));

viewer.show_body(
node,
Expand Down Expand Up @@ -1472,7 +1473,7 @@ impl<T> Snarl<T> {
.map(|idx| OutPin::new(self, OutPinId { node, output: idx }))
.collect::<Vec<_>>();

let node_pos = snarl_state.graph_pos_to_screen(pos, viewport);
let node_pos = snarl_state.graph_pos_to_screen(pos, viewport).round();

// Generate persistent id for the node.
let node_id = snarl_id.with(("snarl-node", node));
Expand Down Expand Up @@ -1581,7 +1582,7 @@ impl<T> Snarl<T> {

let node_ui = &mut ui.new_child(
UiBuilder::new()
.max_rect(node_frame_rect)
.max_rect(rect_round(node_frame_rect))
.layout(Layout::top_down(Align::Center))
.id_salt(node_id),
);
Expand Down Expand Up @@ -1741,14 +1742,15 @@ impl<T> Snarl<T> {

// Show body if there's one.
if viewer.has_body(&self.nodes.get(node.0).unwrap().value) {
let body_left = inputs_rect.right() + ui.spacing().item_spacing.x;
let body_right = outputs_rect.left() - ui.spacing().item_spacing.x;
let body_top = payload_rect.top();
let body_bottom = payload_rect.bottom();

let body_rect = Rect::from_min_max(
pos2(body_left, body_top),
pos2(body_right, body_bottom),
pos2(
inputs_rect.right() + ui.spacing().item_spacing.x,
payload_rect.top(),
),
pos2(
outputs_rect.left() - ui.spacing().item_spacing.x,
payload_rect.bottom(),
),
);

let r = self.draw_body(
Expand Down Expand Up @@ -1779,15 +1781,14 @@ impl<T> Snarl<T> {
NodeLayout::Sandwich => {
// Show input pins.

let inputs_rect = payload_rect;
let r = self.draw_inputs(
viewer,
node,
&inputs,
pin_size,
style,
ui,
inputs_rect,
payload_rect,
payload_clip_rect,
viewport,
input_x,
Expand Down Expand Up @@ -2005,19 +2006,17 @@ impl<T> Snarl<T> {
};

if viewer.has_footer(&self.nodes[node.0].value) {
let footer_left = node_rect.left();
let footer_right = node_rect.right();
let footer_top = pins_rect.bottom() + ui.spacing().item_spacing.y;
let footer_bottom = node_rect.bottom();

let footer_rect = Rect::from_min_max(
pos2(footer_left, footer_top),
pos2(footer_right, footer_bottom),
pos2(
node_rect.left(),
pins_rect.bottom() + ui.spacing().item_spacing.y,
),
pos2(node_rect.right(), node_rect.bottom()),
);

let mut footer_ui = ui.new_child(
UiBuilder::new()
.max_rect(footer_rect)
.max_rect(rect_round(footer_rect))
.layout(Layout::left_to_right(Align::Min))
.id_salt("footer"),
);
Expand Down Expand Up @@ -2053,7 +2052,7 @@ impl<T> Snarl<T> {
// Show node's header
let header_ui: &mut Ui = &mut ui.new_child(
UiBuilder::new()
.max_rect(node_rect + header_frame.total_margin())
.max_rect(rect_round(node_rect + header_frame.total_margin()))
.layout(Layout::top_down(Align::Center))
.id_salt("header"),
);
Expand Down Expand Up @@ -2214,3 +2213,8 @@ const fn snarl_style_is_send_sync() {
const fn is_send_sync<T: Send + Sync>() {}
is_send_sync::<SnarlStyle>();
}

fn rect_round(r: Rect) -> Rect {
// return r;
Rect::from_min_max(r.min.round(), r.max.round())
}
43 changes: 25 additions & 18 deletions src/ui/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,15 +290,15 @@ impl SnarlState {
let mut offset = Vec2::ZERO;
let mut scale = 1.0f32.clamp(style.get_min_scale(), style.get_max_scale());

if bb.is_positive() {
if bb.is_finite() {
bb = bb.expand(100.0);

let bb_size = bb.size();
let viewport_size = viewport.size();

scale = (viewport_size.x / bb_size.x)
scale = (viewport_size.x / bb_size.x.min(1.0))
.min(1.0)
.min(viewport_size.y / bb_size.y)
.min(viewport_size.y / bb_size.y.min(1.0))
.min(style.get_max_scale())
.max(style.get_min_scale());

Expand Down Expand Up @@ -338,36 +338,48 @@ impl SnarlState {
}
}

pub fn set_offset(&mut self, offset: Vec2) {
if self.offset != offset {
self.offset = offset;
self.dirty = true;
}
}

#[inline(always)]
pub fn pan(&mut self, delta: Vec2) {
self.offset += delta;
self.dirty = true;
if delta != Vec2::ZERO {
self.offset += delta;
self.dirty = true;
}
}

#[inline(always)]
pub const fn scale(&self) -> f32 {
self.scale
pub fn scale(&self) -> f32 {
(self.scale * 1024.0).round() / 1024.0
}

#[inline(always)]
pub const fn offset(&self) -> Vec2 {
pub fn offset(&self) -> Vec2 {
self.offset
}

#[inline(always)]
pub fn set_scale(&mut self, scale: f32) {
self.target_scale = scale;
pub fn zoom_delta(&mut self, zoom_delta: f32, velocity: f32, min: f32, max: f32) {
if zoom_delta == 1.0 {
return;
}
self.target_scale = (self.target_scale * zoom_delta.powf(velocity)).clamp(min, max);
self.dirty = true;
}

#[inline(always)]
pub fn screen_pos_to_graph(&self, pos: Pos2, viewport: Rect) -> Pos2 {
(pos + self.offset - viewport.center().to_vec2()) / self.scale
(pos + self.offset() - viewport.center().to_vec2()) / self.scale()
}

#[inline(always)]
pub fn graph_pos_to_screen(&self, pos: Pos2, viewport: Rect) -> Pos2 {
pos * self.scale - self.offset + viewport.center().to_vec2()
pos * self.scale() - self.offset() + viewport.center().to_vec2()
}

#[inline(always)]
Expand All @@ -393,7 +405,7 @@ impl SnarlState {

#[inline(always)]
pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 {
size / self.scale
size / self.scale()
}

// #[inline(always)]
Expand Down Expand Up @@ -524,11 +536,6 @@ impl SnarlState {
self.dirty = true;
}

pub fn set_offset(&mut self, offset: Vec2) {
self.offset = offset;
self.dirty = true;
}

pub fn selected_nodes(&self) -> &[NodeId] {
&self.selected_nodes
}
Expand Down

0 comments on commit 604c385

Please sign in to comment.