khors/vendor/egui-snarl/src/ui/state.rs

405 lines
11 KiB
Rust

use egui::{style::Spacing, Align, Context, Id, Pos2, Rect, Vec2};
use crate::{InPinId, OutPinId, Snarl};
use super::SnarlStyle;
/// Node UI state.
pub struct NodeState {
/// Node size for this frame.
/// It is updated to fit content.
size: Vec2,
header_height: f32,
body_width: f32,
footer_width: f32,
id: Id,
scale: f32,
dirty: bool,
}
#[derive(Clone, Copy, PartialEq)]
struct NodeData {
unscaled_size: Vec2,
unscaled_header_height: f32,
unscaled_body_width: f32,
unsacled_footer_width: f32,
}
impl NodeState {
pub fn load(cx: &Context, id: Id, spacing: &Spacing, scale: f32) -> Self {
match cx.data_mut(|d| d.get_temp::<NodeData>(id)) {
Some(data) => NodeState {
size: data.unscaled_size * scale,
header_height: data.unscaled_header_height * scale,
body_width: data.unscaled_body_width * scale,
footer_width: data.unsacled_footer_width * scale,
id,
scale,
dirty: false,
},
None => Self::initial(id, spacing, scale),
}
}
pub fn clear(self, cx: &Context) {
cx.data_mut(|d| d.remove::<Self>(self.id));
}
pub fn store(&self, cx: &Context) {
if self.dirty {
cx.data_mut(|d| {
d.insert_temp(
self.id,
NodeData {
unscaled_size: self.size / self.scale,
unscaled_header_height: self.header_height / self.scale,
unscaled_body_width: self.body_width / self.scale,
unsacled_footer_width: self.footer_width / self.scale,
},
)
});
}
}
/// Finds node rect at specific position (excluding node frame margin).
pub fn node_rect(&self, pos: Pos2, openness: f32) -> Rect {
Rect::from_min_size(
pos,
egui::vec2(
self.size.x,
f32::max(self.header_height, self.size.y * openness),
),
)
}
pub fn payload_offset(&self, openness: f32) -> f32 {
(self.size.y) * (1.0 - openness)
}
pub fn align_body(&mut self, rect: Rect) -> Rect {
let x_range = Align::Center.align_size_within_range(self.body_width, rect.x_range());
Rect::from_x_y_ranges(x_range, rect.y_range())
}
pub fn align_footer(&mut self, rect: Rect) -> Rect {
let x_range = Align::Center.align_size_within_range(self.footer_width, rect.x_range());
Rect::from_x_y_ranges(x_range, rect.y_range())
}
pub fn set_size(&mut self, size: Vec2) {
if self.size != size {
self.size = size;
self.dirty = true;
}
}
pub fn set_header_height(&mut self, height: f32) {
if self.header_height != height {
self.header_height = height;
self.dirty = true;
}
}
pub fn set_body_width(&mut self, width: f32) {
if self.body_width != width {
self.body_width = width;
self.dirty = true;
}
}
pub fn set_footer_width(&mut self, width: f32) {
if self.footer_width != width {
self.footer_width = width;
self.dirty = true;
}
}
fn initial(id: Id, spacing: &Spacing, scale: f32) -> Self {
NodeState {
size: spacing.interact_size,
header_height: spacing.interact_size.y,
body_width: 0.0,
footer_width: 0.0,
id,
dirty: true,
scale,
}
}
}
#[derive(Clone)]
pub enum NewWires {
In(Vec<InPinId>),
Out(Vec<OutPinId>),
}
pub struct SnarlState {
/// Where viewport's center in graph's space.
offset: Vec2,
/// Scale of the viewport.
scale: f32,
target_scale: f32,
new_wires: Option<NewWires>,
id: Id,
/// Flag indicating that the graph state is dirty must be saved.
dirty: bool,
/// Flag indicating that the link menu is open.
is_link_menu_open: bool,
}
#[derive(Clone)]
struct SnarlStateData {
offset: Vec2,
scale: f32,
target_scale: f32,
new_wires: Option<NewWires>,
is_link_menu_open: bool,
}
impl SnarlState {
pub fn load<T>(
cx: &Context,
id: Id,
pivot: Pos2,
viewport: Rect,
snarl: &Snarl<T>,
style: &SnarlStyle,
) -> Self {
let Some(mut data) = cx.data_mut(|d| d.get_temp::<SnarlStateData>(id)) else {
return Self::initial(id, viewport, snarl, style);
};
let new_scale = cx.animate_value_with_time(id.with("zoom-scale"), data.target_scale, 0.1);
let mut dirty = false;
if new_scale != data.scale {
let a = pivot + data.offset - viewport.center().to_vec2();
data.offset += a * new_scale / data.scale - a;
data.scale = new_scale;
dirty = true;
}
SnarlState {
offset: data.offset,
scale: data.scale,
target_scale: data.target_scale,
new_wires: data.new_wires,
is_link_menu_open: data.is_link_menu_open,
id,
dirty,
}
}
fn initial<T>(id: Id, viewport: Rect, snarl: &Snarl<T>, style: &SnarlStyle) -> Self {
let mut bb = Rect::NOTHING;
for (_, node) in snarl.nodes.iter() {
bb.extend_with(node.pos);
}
if !bb.is_positive() {
let scale = 1.0f32.clamp(style.min_scale, style.max_scale);
return SnarlState {
offset: Vec2::ZERO,
scale,
target_scale: scale,
new_wires: None,
is_link_menu_open: false,
id,
dirty: true,
};
}
bb = bb.expand(100.0);
let bb_size = bb.size();
let viewport_size = viewport.size();
let scale = (viewport_size.x / bb_size.x)
.min(1.0)
.min(viewport_size.y / bb_size.y)
.min(style.max_scale)
.max(style.min_scale);
let offset = bb.center().to_vec2() * scale;
SnarlState {
offset,
scale,
target_scale: scale,
new_wires: None,
is_link_menu_open: false,
id,
dirty: true,
}
}
#[inline(always)]
pub fn store(self, cx: &Context) {
if self.dirty {
cx.data_mut(|d| {
d.insert_temp(
self.id,
SnarlStateData {
offset: self.offset,
scale: self.scale,
target_scale: self.target_scale,
new_wires: self.new_wires,
is_link_menu_open: self.is_link_menu_open,
},
)
});
}
}
#[inline(always)]
pub fn pan(&mut self, delta: Vec2) {
self.offset += delta;
self.dirty = true;
}
#[inline(always)]
pub fn scale(&self) -> f32 {
self.scale
}
#[inline(always)]
pub fn offset(&self) -> Vec2 {
self.offset
}
#[inline(always)]
pub fn set_scale(&mut self, scale: f32) {
self.target_scale = scale;
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
}
#[inline(always)]
pub fn graph_pos_to_screen(&self, pos: Pos2, viewport: Rect) -> Pos2 {
pos * self.scale - self.offset + viewport.center().to_vec2()
}
// #[inline(always)]
// pub fn graph_vec_to_screen(&self, size: Vec2) -> Vec2 {
// size * self.scale
// }
#[inline(always)]
pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 {
size / self.scale
}
// #[inline(always)]
// pub fn graph_value_to_screen(&self, value: f32) -> f32 {
// value * self.scale
// }
// #[inline(always)]
// pub fn screen_value_to_graph(&self, value: f32) -> f32 {
// value / self.scale
// }
pub fn start_new_wire_in(&mut self, pin: InPinId) {
self.new_wires = Some(NewWires::In(vec![pin]));
self.dirty = true;
}
pub fn start_new_wire_out(&mut self, pin: OutPinId) {
self.new_wires = Some(NewWires::Out(vec![pin]));
self.dirty = true;
}
pub fn start_new_wires_in(&mut self, pins: &[InPinId]) {
self.new_wires = Some(NewWires::In(pins.to_vec()));
self.dirty = true;
}
pub fn start_new_wires_out(&mut self, pins: &[OutPinId]) {
self.new_wires = Some(NewWires::Out(pins.to_vec()));
self.dirty = true;
}
pub fn add_new_wire_in(&mut self, pin: InPinId) {
if let Some(NewWires::In(pins)) = &mut self.new_wires {
if !pins.contains(&pin) {
pins.push(pin);
self.dirty = true;
}
}
}
pub fn add_new_wire_out(&mut self, pin: OutPinId) {
if let Some(NewWires::Out(pins)) = &mut self.new_wires {
if !pins.contains(&pin) {
pins.push(pin);
self.dirty = true;
}
}
}
pub fn remove_new_wire_in(&mut self, pin: InPinId) {
if let Some(NewWires::In(pins)) = &mut self.new_wires {
if let Some(idx) = pins.iter().position(|p| *p == pin) {
pins.swap_remove(idx);
self.dirty = true;
}
}
}
pub fn remove_new_wire_out(&mut self, pin: OutPinId) {
if let Some(NewWires::Out(pins)) = &mut self.new_wires {
if let Some(idx) = pins.iter().position(|p| *p == pin) {
pins.swap_remove(idx);
self.dirty = true;
}
}
}
pub fn has_new_wires(&self) -> bool {
self.new_wires.is_some()
}
pub fn new_wires(&self) -> Option<&NewWires> {
self.new_wires.as_ref()
}
pub fn take_wires(&mut self) -> Option<NewWires> {
self.dirty |= self.new_wires.is_some();
self.new_wires.take()
}
pub(crate) fn revert_take_wires(&mut self, wires: NewWires) {
self.new_wires = Some(wires);
}
pub(crate) fn open_link_menu(&mut self) {
self.is_link_menu_open = true;
self.dirty = true;
}
pub(crate) fn close_link_menu(&mut self) {
self.new_wires = None;
self.is_link_menu_open = false;
self.dirty = true;
}
pub(crate) fn is_link_menu_open(&self) -> bool {
self.is_link_menu_open
}
}