Work in screen space coordinates, and offset circle drawing to improve hover detection
This commit is contained in:
parent
0cddc9d9a1
commit
58aad8dbab
@ -1,94 +1,16 @@
|
||||
use egui::emath::RectTransform;
|
||||
use egui::*;
|
||||
use flare_shader::Coefs;
|
||||
use std::ops::Add;
|
||||
|
||||
const HANDLE_RADIUS_IFS: f32 = 0.04;
|
||||
const HANDLE_RADIUS_DRAW_IFS: f32 = HANDLE_RADIUS_IFS / 2.0;
|
||||
/// Radius (in pixels) of the transform element draw circle
|
||||
const ELEMENT_DRAW_RADIUS_PX: f32 = 7.0;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum TransformElement {
|
||||
Origin,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
/// Stroke size (in pixels) of the transform element draw circle
|
||||
const ELEMENT_DRAW_STROKE_PX: f32 = 2.0;
|
||||
|
||||
/// Affine coefficients expressed as three points of a triangle
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CoefsTriangle {
|
||||
origin: Pos2,
|
||||
x: Pos2,
|
||||
y: Pos2,
|
||||
}
|
||||
|
||||
impl CoefsTriangle {
|
||||
pub fn new(origin: Pos2, x: Pos2, y: Pos2) -> Self {
|
||||
Self { origin, x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Coefs> for CoefsTriangle {
|
||||
fn from(value: Coefs) -> Self {
|
||||
let origin = pos2(value.c, -value.f);
|
||||
Self {
|
||||
origin,
|
||||
x: origin + vec2(value.a, -value.d),
|
||||
y: origin + vec2(-value.b, value.e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Coefs> for CoefsTriangle {
|
||||
fn into(self) -> Coefs {
|
||||
Coefs {
|
||||
a: self.x.x - self.origin.x,
|
||||
b: self.origin.x - self.y.x,
|
||||
c: self.origin.x,
|
||||
d: self.origin.y - self.x.y,
|
||||
e: self.y.y - self.origin.y,
|
||||
f: -self.origin.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget for manipulating IFS transform affine coefficients
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct TransformEditor {
|
||||
/// Center point (in IFS coordinates) of the editor window
|
||||
center_ifs: Pos2,
|
||||
|
||||
/// Total range (in IFS coordinates) of the editor window
|
||||
range_ifs: f32,
|
||||
|
||||
/// Hover position (in IFS coordinates) of the cursor.
|
||||
///
|
||||
/// Because of input latency during large movements, `egui`'s drag motion
|
||||
/// doesn't precisely match the prior cursor position. Track the position
|
||||
/// here to calculate an exact update from the last frame to now.
|
||||
hover_pos_ifs: Option<Pos2>,
|
||||
|
||||
/// Index of the transform the cursor is hovering over
|
||||
hover_index: Option<usize>,
|
||||
|
||||
/// For the hovered transform, which specific element the cursor is hovering over.
|
||||
/// Assumed to always have a value when `hover_index` has a value
|
||||
hover_element: Option<TransformElement>,
|
||||
|
||||
/// Index of the transform being dragged
|
||||
drag_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for TransformEditor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
center_ifs: Pos2::ZERO,
|
||||
range_ifs: 4.0,
|
||||
hover_pos_ifs: None,
|
||||
hover_index: None,
|
||||
hover_element: None,
|
||||
drag_index: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Offset (in pixels) of the transform element draw circle
|
||||
const ELEMENT_DRAW_OFFSET_PX: Vec2 = vec2(-2.0, -2.0);
|
||||
|
||||
fn test_in_circle(pt: Pos2, center: Pos2, radius: f32) -> bool {
|
||||
((pt.x - center.x).powf(2.0) + (pt.y - center.y).powf(2.0)) <= radius.powf(2.0)
|
||||
@ -110,36 +32,145 @@ fn test_in_triangle(pt: Pos2, v1: Pos2, v2: Pos2, v3: Pos2) -> bool {
|
||||
!(has_neg && has_pos)
|
||||
}
|
||||
|
||||
/// Test whether the provided position is hovering on the transform
|
||||
fn test_hovered(hover_pos: Option<Pos2>, coefs: Coefs) -> Option<TransformElement> {
|
||||
let coefs_triangle: CoefsTriangle = coefs.into();
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum TransformElement {
|
||||
Origin,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
if hover_pos.is_none() {
|
||||
return None;
|
||||
/// Affine coefficients expressed as three points of a triangle
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct TransformTriangle {
|
||||
origin: Pos2,
|
||||
x: Pos2,
|
||||
y: Pos2,
|
||||
}
|
||||
|
||||
impl TransformTriangle {
|
||||
pub fn new(origin: Pos2, x: Pos2, y: Pos2) -> Self {
|
||||
Self { origin, x, y }
|
||||
}
|
||||
|
||||
let hover_pos = hover_pos.unwrap();
|
||||
if test_in_circle(hover_pos, coefs_triangle.x, HANDLE_RADIUS_IFS) {
|
||||
Some(TransformElement::X)
|
||||
} else if test_in_circle(hover_pos, coefs_triangle.y, HANDLE_RADIUS_IFS) {
|
||||
Some(TransformElement::Y)
|
||||
} else if test_in_circle(hover_pos, coefs_triangle.origin, HANDLE_RADIUS_IFS)
|
||||
|| test_in_triangle(
|
||||
hover_pos,
|
||||
coefs_triangle.origin,
|
||||
coefs_triangle.x,
|
||||
coefs_triangle.y,
|
||||
)
|
||||
{
|
||||
Some(TransformElement::Origin)
|
||||
} else {
|
||||
None
|
||||
pub fn interact_drag(self, element: TransformElement, drag_delta: Vec2) -> Self {
|
||||
match element {
|
||||
TransformElement::X => Self::new(self.origin, self.x + drag_delta, self.y),
|
||||
TransformElement::Y => Self::new(self.origin, self.x, self.y + drag_delta),
|
||||
TransformElement::Origin => Self::new(
|
||||
self.origin + drag_delta,
|
||||
self.x + drag_delta,
|
||||
self.y + drag_delta,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_pos(self, rect: RectTransform) -> Self {
|
||||
Self {
|
||||
origin: rect.transform_pos(self.origin),
|
||||
x: rect.transform_pos(self.x),
|
||||
y: rect.transform_pos(self.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_hovered(
|
||||
&self,
|
||||
hover_pos: Pos2,
|
||||
ifs_to_screen: RectTransform,
|
||||
) -> Option<TransformElement> {
|
||||
let origin_pos = ifs_to_screen.transform_pos(self.origin);
|
||||
let x_pos = ifs_to_screen.transform_pos(self.x);
|
||||
let y_pos = ifs_to_screen.transform_pos(self.y);
|
||||
|
||||
if test_in_circle(hover_pos, x_pos, ELEMENT_DRAW_RADIUS_PX) {
|
||||
Some(TransformElement::X)
|
||||
} else if test_in_circle(hover_pos, y_pos, ELEMENT_DRAW_RADIUS_PX) {
|
||||
Some(TransformElement::Y)
|
||||
} else if test_in_circle(hover_pos, origin_pos, ELEMENT_DRAW_RADIUS_PX)
|
||||
|| test_in_triangle(hover_pos, origin_pos, x_pos, y_pos)
|
||||
{
|
||||
Some(TransformElement::Origin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_viewport_ifs(interact_rect: Rect, center_ifs: Pos2, range_ifs: f32) -> Rect {
|
||||
let aspect_ratio = interact_rect.width() / interact_rect.height();
|
||||
impl From<Coefs> for TransformTriangle {
|
||||
fn from(value: Coefs) -> Self {
|
||||
let origin = pos2(value.c, -value.f);
|
||||
Self {
|
||||
origin,
|
||||
x: origin + vec2(value.a, -value.d),
|
||||
y: origin + vec2(-value.b, value.e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Coefs> for TransformTriangle {
|
||||
fn into(self) -> Coefs {
|
||||
Coefs {
|
||||
a: self.x.x - self.origin.x,
|
||||
b: self.origin.x - self.y.x,
|
||||
c: self.origin.x,
|
||||
d: self.origin.y - self.x.y,
|
||||
e: self.y.y - self.origin.y,
|
||||
f: -self.origin.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vec2> for TransformTriangle {
|
||||
type Output = TransformTriangle;
|
||||
|
||||
fn add(self, rhs: Vec2) -> Self::Output {
|
||||
Self {
|
||||
origin: self.origin + rhs,
|
||||
x: self.x + rhs,
|
||||
y: self.y + rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget for manipulating IFS transform affine coefficients
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct TransformEditor {
|
||||
/// Center point (in IFS coordinates) of the editor window
|
||||
center_ifs: Pos2,
|
||||
|
||||
/// Total range (in IFS coordinates) of the editor window
|
||||
range_ifs: f32,
|
||||
|
||||
/// Hover position (in screen coordinates) of the cursor on the previous update.
|
||||
///
|
||||
/// Because of input latency during large drag motions, `egui`'s drag motion
|
||||
/// isn't precise enough to update transform coefficients. Instead, track the
|
||||
/// cursor position directly
|
||||
hover_pos: Option<Pos2>,
|
||||
|
||||
/// Transform index the cursor is hovering over
|
||||
hover_index: Option<usize>,
|
||||
|
||||
/// Specific element of the transform hovered by the cursor
|
||||
hover_element: Option<TransformElement>,
|
||||
|
||||
/// Transform index the cursor is dragging
|
||||
drag_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for TransformEditor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
center_ifs: Pos2::ZERO,
|
||||
range_ifs: 4.0,
|
||||
hover_pos: None,
|
||||
hover_index: None,
|
||||
hover_element: None,
|
||||
drag_index: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_viewport_ifs(aspect_ratio: f32, center_ifs: Pos2, range_ifs: f32) -> Rect {
|
||||
let size_ifs = if aspect_ratio >= 1.0 {
|
||||
vec2(range_ifs * aspect_ratio, range_ifs)
|
||||
} else {
|
||||
@ -148,8 +179,8 @@ fn build_viewport_ifs(interact_rect: Rect, center_ifs: Pos2, range_ifs: f32) ->
|
||||
let min_ifs = center_ifs - size_ifs / 2.0;
|
||||
let max_ifs = center_ifs + size_ifs / 2.0;
|
||||
|
||||
// IFS coordinates follow the screen coordinate "value increases from top left to bottom right"
|
||||
// convention, so the Y-axis is flipped here to behave like a Cartesian plot
|
||||
// IFS coordinates follow the "value increases from top left to bottom right" convention.
|
||||
// Because we want coordinates to behave like a Cartesian plot, the Y-axis is flipped
|
||||
Rect::from_min_max(pos2(min_ifs.x, max_ifs.y), pos2(max_ifs.x, min_ifs.y))
|
||||
}
|
||||
|
||||
@ -163,145 +194,148 @@ impl TransformEditor {
|
||||
}
|
||||
|
||||
let interact_rect = response.interact_rect;
|
||||
let ifs_rect = build_viewport_ifs(interact_rect, self.center_ifs, self.range_ifs);
|
||||
let pixels_per_unit_ifs = interact_rect.width() / ifs_rect.width();
|
||||
let ifs_rect = build_viewport_ifs(
|
||||
interact_rect.aspect_ratio(),
|
||||
self.center_ifs,
|
||||
self.range_ifs,
|
||||
);
|
||||
|
||||
// Update internal state based on screen interactions, then paint to screen
|
||||
let to_ifs = RectTransform::from_to(interact_rect, ifs_rect);
|
||||
let hover_pos_ifs = response.hover_pos().map(|p| to_ifs.transform_pos(p));
|
||||
let focus_index = self.interact_update(
|
||||
hover_pos_ifs,
|
||||
response.clicked(),
|
||||
response.drag_started(),
|
||||
response.drag_stopped(),
|
||||
let ifs_to_screen = RectTransform::from_to(ifs_rect, interact_rect);
|
||||
self.interact_update(
|
||||
ui.input(|i| i.pointer.interact_pos()),
|
||||
ui.input(|i| i.pointer.primary_pressed()),
|
||||
ui.input(|i| i.pointer.primary_released()),
|
||||
ifs_to_screen,
|
||||
transforms,
|
||||
);
|
||||
|
||||
let to_screen = RectTransform::from_to(ifs_rect, interact_rect);
|
||||
self.interact_draw(painter, to_screen, pixels_per_unit_ifs, transforms);
|
||||
self.interact_draw(painter, ifs_to_screen, transforms);
|
||||
|
||||
focus_index
|
||||
self.drag_index
|
||||
}
|
||||
|
||||
/// Update state of the provided transform coefficients based on current interactions.
|
||||
///
|
||||
/// Assumes that positions/vectors are in IFS coordinates
|
||||
/// Update state of the provided transform coefficients based on current interactions,
|
||||
/// return the transform coefficients that have claimed focus (if any)
|
||||
fn interact_update(
|
||||
&mut self,
|
||||
hover_pos: Option<Pos2>,
|
||||
clicked: bool,
|
||||
drag_started: bool,
|
||||
drag_stopped: bool,
|
||||
primary_pressed: bool,
|
||||
primary_released: bool,
|
||||
ifs_to_screen: RectTransform,
|
||||
transforms: &mut [Coefs],
|
||||
) -> Option<usize> {
|
||||
// Check each transform to see if it is hovered (giving priority to the currently hovered transform),
|
||||
let mut hover_found = false;
|
||||
if self.hover_index.is_some_and(|i| i < transforms.len()) {
|
||||
let hover_element =
|
||||
test_hovered(self.hover_pos_ifs, transforms[self.hover_index.unwrap()]);
|
||||
if hover_element.is_some() {
|
||||
hover_found = true;
|
||||
self.hover_element = hover_element;
|
||||
}
|
||||
) {
|
||||
// If the cursor is not in this widget, reset state
|
||||
if hover_pos.is_none() {
|
||||
self.hover_pos = None;
|
||||
self.hover_index = None;
|
||||
self.hover_element = None;
|
||||
self.drag_index = None;
|
||||
return;
|
||||
}
|
||||
|
||||
let hover_pos = hover_pos.unwrap();
|
||||
|
||||
// If the transform array was modified, reset state and then proceed
|
||||
if self.hover_index.map_or(false, |i| i >= transforms.len())
|
||||
|| self.drag_index.map_or(false, |i| i >= transforms.len())
|
||||
{
|
||||
self.hover_index = None;
|
||||
self.hover_element = None;
|
||||
self.drag_index = None;
|
||||
}
|
||||
|
||||
// If a transform is being dragged, update its position
|
||||
if self.drag_index.is_some() {
|
||||
let last_hover_pos = self.hover_pos.unwrap();
|
||||
let hover_index = self.hover_index.unwrap();
|
||||
let hover_element = self.hover_element.unwrap();
|
||||
|
||||
let drag_delta_ifs = (hover_pos - last_hover_pos) / ifs_to_screen.scale();
|
||||
let transform_triangle: TransformTriangle = transforms[hover_index].into();
|
||||
transforms[hover_index] = transform_triangle
|
||||
.interact_drag(hover_element, drag_delta_ifs)
|
||||
.into();
|
||||
}
|
||||
|
||||
// Store the hover pos for use on the next update
|
||||
self.hover_pos = Some(hover_pos);
|
||||
|
||||
// Check if the currently-hovered transform is still hovered
|
||||
let mut hover_found = false;
|
||||
|
||||
if let Some(hover_index) = self.hover_index {
|
||||
let transform_triangle: TransformTriangle = transforms[hover_index].into();
|
||||
self.hover_element = transform_triangle.is_hovered(hover_pos, ifs_to_screen);
|
||||
hover_found = self.hover_element.is_some();
|
||||
}
|
||||
|
||||
// Check if any transform is hovered
|
||||
if !hover_found {
|
||||
for (i, transform) in transforms.iter().enumerate() {
|
||||
if let Some(hover_element) = test_hovered(self.hover_pos_ifs, *transform) {
|
||||
let transform_triangle: TransformTriangle = (*transform).into();
|
||||
self.hover_element = transform_triangle.is_hovered(hover_pos, ifs_to_screen);
|
||||
|
||||
if self.hover_element.is_some() {
|
||||
hover_found = true;
|
||||
self.hover_index = Some(i);
|
||||
self.hover_element = Some(hover_element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hover_found {
|
||||
// No transforms are hovered, clear interaction state and return
|
||||
self.hover_pos_ifs = hover_pos;
|
||||
// No hovers found, reset state
|
||||
self.hover_index = None;
|
||||
self.hover_element = None;
|
||||
self.drag_index = None;
|
||||
return None;
|
||||
}
|
||||
|
||||
let hover_delta =
|
||||
if let (Some(current_hover_pos), Some(hover_pos)) = (hover_pos, self.hover_pos_ifs) {
|
||||
current_hover_pos - hover_pos
|
||||
} else {
|
||||
Vec2::ZERO
|
||||
};
|
||||
self.hover_pos_ifs = hover_pos;
|
||||
|
||||
// If the hovered transform is clicked, it receives focus and we end updates
|
||||
if clicked {
|
||||
return self.hover_index;
|
||||
}
|
||||
|
||||
// If the hovered transform is dragged, it receives focus and the drag index is updated
|
||||
if drag_started {
|
||||
// Check drag state
|
||||
if primary_pressed && self.hover_index.is_some() {
|
||||
self.drag_index = self.hover_index;
|
||||
}
|
||||
|
||||
// If there is a transform being dragged, update its position
|
||||
if self.drag_index.is_some() && hover_delta.abs().max_elem() > 0.0 {
|
||||
let mut coefs = &mut transforms[self.drag_index.unwrap()];
|
||||
let mut coefs_triangle: CoefsTriangle = (*coefs).into();
|
||||
|
||||
match self.hover_element.unwrap() {
|
||||
TransformElement::X => coefs_triangle.x += hover_delta,
|
||||
TransformElement::Y => coefs_triangle.y += hover_delta,
|
||||
TransformElement::Origin => {
|
||||
coefs_triangle.x += hover_delta;
|
||||
coefs_triangle.y += hover_delta;
|
||||
coefs_triangle.origin += hover_delta;
|
||||
}
|
||||
}
|
||||
|
||||
*coefs = coefs_triangle.into();
|
||||
}
|
||||
|
||||
// If the transform is no longer being dragged, clear the drag index
|
||||
if drag_stopped {
|
||||
if primary_released {
|
||||
self.drag_index = None;
|
||||
}
|
||||
|
||||
self.drag_index
|
||||
}
|
||||
|
||||
fn interact_draw_transform(
|
||||
hover_element: Option<TransformElement>,
|
||||
painter: &Painter,
|
||||
to_screen: RectTransform,
|
||||
pixels_per_unit_ifs: f32,
|
||||
ifs_to_screen: RectTransform,
|
||||
transform: Coefs,
|
||||
hovered_element: Option<TransformElement>,
|
||||
) {
|
||||
let coefs_triangle: CoefsTriangle = transform.into();
|
||||
let origin_screen = to_screen.transform_pos(coefs_triangle.origin);
|
||||
let x_screen = to_screen.transform_pos(coefs_triangle.x);
|
||||
let y_screen = to_screen.transform_pos(coefs_triangle.y);
|
||||
// `epaint` doesn't provide an option for whether the stroke is drawn inside, in the middle,
|
||||
// or outside the shape to paint. In manual testing, hover detection works best when assuming
|
||||
// the stroke is outside the shape.
|
||||
// Also in manual testing, hover detection seems to work best when drawing the circle at
|
||||
// a slight offset to the actual center position. Not clear why.
|
||||
let transform_triangle: TransformTriangle = transform.into();
|
||||
let draw_triangle =
|
||||
transform_triangle.transform_pos(ifs_to_screen) + ELEMENT_DRAW_OFFSET_PX;
|
||||
|
||||
let stroke = Stroke::new(2.0, Color32::BLUE);
|
||||
let stroke = Stroke::new(ELEMENT_DRAW_STROKE_PX, Color32::BLUE);
|
||||
painter.circle_stroke(
|
||||
origin_screen,
|
||||
HANDLE_RADIUS_DRAW_IFS * pixels_per_unit_ifs,
|
||||
draw_triangle.origin,
|
||||
ELEMENT_DRAW_RADIUS_PX - ELEMENT_DRAW_STROKE_PX,
|
||||
stroke,
|
||||
);
|
||||
painter.circle_stroke(
|
||||
x_screen,
|
||||
HANDLE_RADIUS_DRAW_IFS * pixels_per_unit_ifs,
|
||||
draw_triangle.x,
|
||||
ELEMENT_DRAW_RADIUS_PX - ELEMENT_DRAW_STROKE_PX,
|
||||
stroke,
|
||||
);
|
||||
painter.circle_stroke(
|
||||
y_screen,
|
||||
HANDLE_RADIUS_DRAW_IFS * pixels_per_unit_ifs,
|
||||
draw_triangle.y,
|
||||
ELEMENT_DRAW_RADIUS_PX - ELEMENT_DRAW_STROKE_PX,
|
||||
stroke,
|
||||
);
|
||||
|
||||
let body_alpha: u8 = if hovered_element.is_some() { 8 } else { 0 };
|
||||
let body_alpha: u8 = if hover_element.is_some() { 8 } else { 0 };
|
||||
let body_fill = Color32::from_rgba_unmultiplied(0, 0, u8::MAX, body_alpha);
|
||||
let body = Shape::convex_polygon(
|
||||
vec![origin_screen, x_screen, y_screen],
|
||||
vec![draw_triangle.origin, draw_triangle.x, draw_triangle.y],
|
||||
body_fill,
|
||||
stroke,
|
||||
);
|
||||
@ -309,35 +343,21 @@ impl TransformEditor {
|
||||
}
|
||||
|
||||
/// Draw the provided transform coefficients to the screen.
|
||||
fn interact_draw(
|
||||
&self,
|
||||
painter: Painter,
|
||||
to_screen: RectTransform,
|
||||
pixels_per_unit_ifs: f32,
|
||||
transforms: &[Coefs],
|
||||
) {
|
||||
// Hovered transform is painted at the end so it has priority
|
||||
fn interact_draw(&self, painter: Painter, ifs_to_screen: RectTransform, transforms: &[Coefs]) {
|
||||
transforms
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| Some(*i) != self.hover_index)
|
||||
.for_each(|(_, transform)| {
|
||||
TransformEditor::interact_draw_transform(
|
||||
&painter,
|
||||
to_screen,
|
||||
pixels_per_unit_ifs,
|
||||
*transform,
|
||||
None
|
||||
)
|
||||
Self::interact_draw_transform(None, &painter, ifs_to_screen, *transform)
|
||||
});
|
||||
|
||||
self.hover_index.map(|i| {
|
||||
TransformEditor::interact_draw_transform(
|
||||
&painter,
|
||||
to_screen,
|
||||
pixels_per_unit_ifs,
|
||||
transforms[i],
|
||||
Self::interact_draw_transform(
|
||||
self.hover_element,
|
||||
&painter,
|
||||
ifs_to_screen,
|
||||
transforms[i],
|
||||
)
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user