mirror of https://codeberg.org/topola/topola.git
refactor(topola-egui): Move InteractiveEvent handling from Viewport into Workspace
This commit is contained in:
parent
c66089bca9
commit
c8848ef269
|
|
@ -131,13 +131,14 @@ impl Overlay {
|
||||||
_board: &Board<impl AccessMesadata>,
|
_board: &Board<impl AccessMesadata>,
|
||||||
_appearance_panel: &AppearancePanel,
|
_appearance_panel: &AppearancePanel,
|
||||||
at: Point,
|
at: Point,
|
||||||
modifiers: &egui::Modifiers,
|
ctrl: bool,
|
||||||
|
shift: bool,
|
||||||
) {
|
) {
|
||||||
if self.reselect_bbox.is_none() {
|
if self.reselect_bbox.is_none() {
|
||||||
// handle bounding box selection
|
// handle bounding box selection
|
||||||
let selmode = if modifiers.ctrl {
|
let selmode = if ctrl {
|
||||||
SelectionMode::Toggling
|
SelectionMode::Toggling
|
||||||
} else if modifiers.shift {
|
} else if shift {
|
||||||
SelectionMode::Addition
|
SelectionMode::Addition
|
||||||
} else {
|
} else {
|
||||||
SelectionMode::Substitution
|
SelectionMode::Substitution
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use core::ops::ControlFlow;
|
|
||||||
use geo::point;
|
use geo::point;
|
||||||
use petgraph::{
|
use petgraph::{
|
||||||
data::DataMap,
|
data::DataMap,
|
||||||
|
|
@ -10,12 +9,8 @@ use petgraph::{
|
||||||
};
|
};
|
||||||
use rstar::{Envelope, AABB};
|
use rstar::{Envelope, AABB};
|
||||||
use topola::{
|
use topola::{
|
||||||
autorouter::{
|
autorouter::invoker::{
|
||||||
execution::Command,
|
GetGhosts, GetMaybeNavcord, GetMaybeThetastarStepper, GetNavmeshDebugTexts, GetObstacles,
|
||||||
invoker::{
|
|
||||||
GetGhosts, GetMaybeNavcord, GetMaybeThetastarStepper, GetNavmeshDebugTexts,
|
|
||||||
GetObstacles,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
board::AccessMesadata,
|
board::AccessMesadata,
|
||||||
drawing::{
|
drawing::{
|
||||||
|
|
@ -25,10 +20,10 @@ use topola::{
|
||||||
geometry::{shape::AccessShape, GenericNode},
|
geometry::{shape::AccessShape, GenericNode},
|
||||||
graph::MakeRef,
|
graph::MakeRef,
|
||||||
interactor::{
|
interactor::{
|
||||||
activity::{ActivityStepper, InteractiveEvent, InteractiveInput},
|
activity::{ActivityStepper, InteractiveEvent, InteractiveEventKind, InteractiveInput},
|
||||||
interaction::InteractionStepper,
|
interaction::InteractionStepper,
|
||||||
},
|
},
|
||||||
layout::{poly::MakePolygon, via::ViaWeight},
|
layout::poly::MakePolygon,
|
||||||
math::{Circle, RotationSense},
|
math::{Circle, RotationSense},
|
||||||
router::navmesh::NavnodeIndex,
|
router::navmesh::NavnodeIndex,
|
||||||
};
|
};
|
||||||
|
|
@ -138,95 +133,52 @@ impl Viewport {
|
||||||
if let Some(workspace) = maybe_workspace {
|
if let Some(workspace) = maybe_workspace {
|
||||||
let latest_point = point! {x: latest_pos.x as f64, y: -latest_pos.y as f64};
|
let latest_point = point! {x: latest_pos.x as f64, y: -latest_pos.y as f64};
|
||||||
|
|
||||||
// Advances the app's state by the delta time `dt`. May call
|
let interactive_input = InteractiveInput {
|
||||||
// `.update_state()` more than once if the delta time is more than a multiple of
|
active_layer: workspace.appearance_panel.active_layer,
|
||||||
// the timestep.
|
pointer_pos: latest_point,
|
||||||
let dt = ctx.input(|i| i.stable_dt);
|
dt: ctx.input(|i| i.stable_dt),
|
||||||
let active_layer = workspace.appearance_panel.active_layer;
|
};
|
||||||
self.update_counter += dt;
|
workspace.advance_state_by_dt(
|
||||||
while self.update_counter >= menu_bar.frame_timestep {
|
tr,
|
||||||
self.update_counter -= menu_bar.frame_timestep;
|
error_dialog,
|
||||||
if let ControlFlow::Break(()) = workspace.update_state(
|
menu_bar.frame_timestep,
|
||||||
tr,
|
&interactive_input,
|
||||||
error_dialog,
|
);
|
||||||
&InteractiveInput {
|
|
||||||
active_layer,
|
|
||||||
pointer_pos: point! {x: latest_pos.x as f64, y: latest_pos.y as f64},
|
|
||||||
dt,
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !workspace.interactor.maybe_activity().as_ref().map_or(true, |activity| {
|
let interactive_event_kind =
|
||||||
matches!(activity.maybe_status(), Some(ControlFlow::Break(..)))
|
if response.clicked_by(egui::PointerButton::Primary) {
|
||||||
}) {
|
Some(InteractiveEventKind::PointerPrimaryButtonClicked)
|
||||||
// there is currently some activity
|
} else if response.drag_started_by(egui::PointerButton::Primary) {
|
||||||
let interactive_event = if response.clicked_by(egui::PointerButton::Primary) {
|
Some(InteractiveEventKind::PointerPrimaryButtonDragStarted)
|
||||||
Some(InteractiveEvent::PointerPrimaryButtonClicked)
|
} else if response.drag_stopped_by(egui::PointerButton::Primary) {
|
||||||
|
Some(InteractiveEventKind::PointerPrimaryButtonDragStopped)
|
||||||
} else if response.clicked_by(egui::PointerButton::Secondary) {
|
} else if response.clicked_by(egui::PointerButton::Secondary) {
|
||||||
Some(InteractiveEvent::PointerSecondaryButtonClicked)
|
Some(InteractiveEventKind::PointerSecondaryButtonClicked)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if let Some(interactive_event) = interactive_event {
|
if let Some(kind) = interactive_event_kind {
|
||||||
let dt = ctx.input(|i| i.stable_dt);
|
let (ctrl, shift) = response
|
||||||
let active_layer = workspace.appearance_panel.active_layer;
|
.ctx
|
||||||
let _ = workspace.update_state_for_event(
|
.input(|i| (i.modifiers.ctrl, i.modifiers.shift));
|
||||||
tr,
|
let _ = workspace.update_state_for_event(
|
||||||
error_dialog,
|
tr,
|
||||||
&InteractiveInput {
|
error_dialog,
|
||||||
active_layer,
|
menu_bar,
|
||||||
pointer_pos: latest_point,
|
&interactive_input,
|
||||||
dt,
|
InteractiveEvent { kind, ctrl, shift },
|
||||||
},
|
);
|
||||||
interactive_event,
|
} else if let Some((_, bsk, cur_bbox)) =
|
||||||
);
|
workspace.overlay.get_bbox_reselect(latest_point)
|
||||||
}
|
{
|
||||||
} else {
|
use topola::autorouter::selection::BboxSelectionKind;
|
||||||
let layers = &mut workspace.appearance_panel;
|
painter.paint_bbox_with_color(
|
||||||
let overlay = &mut workspace.overlay;
|
cur_bbox,
|
||||||
let board = workspace.interactor.invoker().autorouter().board();
|
match bsk {
|
||||||
if response.clicked_by(egui::PointerButton::Primary) {
|
BboxSelectionKind::CompletelyInside => egui::Color32::YELLOW,
|
||||||
if menu_bar.is_placing_via {
|
BboxSelectionKind::MerelyIntersects => egui::Color32::BLUE,
|
||||||
workspace.interactor.execute(Command::PlaceVia(ViaWeight {
|
},
|
||||||
from_layer: 0,
|
);
|
||||||
to_layer: 0,
|
|
||||||
circle: Circle {
|
|
||||||
pos: latest_point,
|
|
||||||
r: menu_bar
|
|
||||||
.autorouter_options
|
|
||||||
.router_options
|
|
||||||
.routed_band_width
|
|
||||||
/ 2.0,
|
|
||||||
},
|
|
||||||
maybe_net: Some(1234),
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
overlay.click(board, layers, latest_point);
|
|
||||||
}
|
|
||||||
} else if response.drag_started_by(egui::PointerButton::Primary) {
|
|
||||||
overlay.drag_start(
|
|
||||||
board,
|
|
||||||
layers,
|
|
||||||
latest_point,
|
|
||||||
&response.ctx.input(|i| i.modifiers),
|
|
||||||
);
|
|
||||||
} else if response.drag_stopped_by(egui::PointerButton::Primary) {
|
|
||||||
overlay.drag_stop(board, layers, latest_point);
|
|
||||||
} else if let Some((_, bsk, cur_bbox)) =
|
|
||||||
overlay.get_bbox_reselect(latest_point)
|
|
||||||
{
|
|
||||||
use topola::autorouter::selection::BboxSelectionKind;
|
|
||||||
painter.paint_bbox_with_color(
|
|
||||||
cur_bbox,
|
|
||||||
match bsk {
|
|
||||||
BboxSelectionKind::CompletelyInside => egui::Color32::YELLOW,
|
|
||||||
BboxSelectionKind::MerelyIntersects => egui::Color32::BLUE,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let layers = &mut workspace.appearance_panel;
|
let layers = &mut workspace.appearance_panel;
|
||||||
|
|
@ -529,8 +481,13 @@ impl Viewport {
|
||||||
.paint_primitive(ghost, egui::Color32::from_rgb(75, 75, 150));
|
.paint_primitive(ghost, egui::Color32::from_rgb(75, 75, 150));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ActivityStepper::Interaction(InteractionStepper::RoutePlan(rp)) = activity.activity() {
|
if let ActivityStepper::Interaction(InteractionStepper::RoutePlan(rp)) =
|
||||||
painter.paint_linestring(&rp.lines, egui::Color32::from_rgb(245, 182, 66));
|
activity.activity()
|
||||||
|
{
|
||||||
|
painter.paint_linestring(
|
||||||
|
&rp.lines,
|
||||||
|
egui::Color32::from_rgb(245, 182, 66),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref navmesh) =
|
if let Some(ref navmesh) =
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,19 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use topola::{
|
use topola::{
|
||||||
autorouter::history::History,
|
autorouter::{execution::Command, history::History},
|
||||||
interactor::{
|
interactor::{
|
||||||
activity::{InteractiveEvent, InteractiveInput},
|
activity::{InteractiveEvent, InteractiveEventKind, InteractiveInput},
|
||||||
Interactor,
|
Interactor,
|
||||||
},
|
},
|
||||||
layout::LayoutEdit,
|
layout::{via::ViaWeight, LayoutEdit},
|
||||||
|
math::Circle,
|
||||||
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
|
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
appearance_panel::AppearancePanel, error_dialog::ErrorDialog, overlay::Overlay,
|
appearance_panel::AppearancePanel, error_dialog::ErrorDialog, menu_bar::MenuBar,
|
||||||
translator::Translator,
|
overlay::Overlay, translator::Translator,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A loaded design and associated structures.
|
/// A loaded design and associated structures.
|
||||||
|
|
@ -33,6 +34,8 @@ pub struct Workspace {
|
||||||
Sender<std::io::Result<Result<History, serde_json::Error>>>,
|
Sender<std::io::Result<Result<History, serde_json::Error>>>,
|
||||||
Receiver<std::io::Result<Result<History, serde_json::Error>>>,
|
Receiver<std::io::Result<Result<History, serde_json::Error>>>,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
update_counter: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
|
|
@ -58,25 +61,94 @@ impl Workspace {
|
||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
history_channel: channel(),
|
history_channel: channel(),
|
||||||
|
update_counter: 0.0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Advances the app's state by the delta time `dt`. May call
|
||||||
|
/// `.update_state()` more than once if the delta time is more than a multiple of
|
||||||
|
/// the timestep.
|
||||||
|
pub fn advance_state_by_dt(
|
||||||
|
&mut self,
|
||||||
|
tr: &Translator,
|
||||||
|
error_dialog: &mut ErrorDialog,
|
||||||
|
frame_timestep: f32,
|
||||||
|
interactive_input: &InteractiveInput,
|
||||||
|
) {
|
||||||
|
self.update_counter += interactive_input.dt;
|
||||||
|
while self.update_counter >= frame_timestep {
|
||||||
|
self.update_counter -= frame_timestep;
|
||||||
|
if let ControlFlow::Break(()) = self.update_state(tr, error_dialog, interactive_input) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_state_for_event(
|
pub fn update_state_for_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
tr: &Translator,
|
tr: &Translator,
|
||||||
error_dialog: &mut ErrorDialog,
|
error_dialog: &mut ErrorDialog,
|
||||||
|
menu_bar: &MenuBar,
|
||||||
interactive_input: &InteractiveInput,
|
interactive_input: &InteractiveInput,
|
||||||
interactive_event: InteractiveEvent,
|
interactive_event: InteractiveEvent,
|
||||||
) -> ControlFlow<()> {
|
) {
|
||||||
match self
|
if !self
|
||||||
.interactor
|
.interactor
|
||||||
.update_for_event(interactive_input, interactive_event)
|
.maybe_activity()
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |activity| {
|
||||||
|
matches!(activity.maybe_status(), Some(ControlFlow::Break(..)))
|
||||||
|
})
|
||||||
{
|
{
|
||||||
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
match self
|
||||||
ControlFlow::Break(Ok(())) => ControlFlow::Break(()),
|
.interactor
|
||||||
ControlFlow::Break(Err(err)) => {
|
.update_for_event(interactive_input, interactive_event)
|
||||||
error_dialog.push_error("tr-module-invoker", format!("{}", err));
|
{
|
||||||
ControlFlow::Break(())
|
ControlFlow::Continue(()) | ControlFlow::Break(Ok(())) => {}
|
||||||
|
ControlFlow::Break(Err(err)) => {
|
||||||
|
error_dialog.push_error("tr-module-invoker", format!("{}", err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let board = self.interactor.invoker().autorouter().board();
|
||||||
|
match interactive_event.kind {
|
||||||
|
InteractiveEventKind::PointerPrimaryButtonClicked => {
|
||||||
|
if menu_bar.is_placing_via {
|
||||||
|
self.interactor.execute(Command::PlaceVia(ViaWeight {
|
||||||
|
from_layer: 0,
|
||||||
|
to_layer: 0,
|
||||||
|
circle: Circle {
|
||||||
|
pos: interactive_input.pointer_pos,
|
||||||
|
r: menu_bar.autorouter_options.router_options.routed_band_width
|
||||||
|
/ 2.0,
|
||||||
|
},
|
||||||
|
maybe_net: Some(1234),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
self.overlay.click(
|
||||||
|
board,
|
||||||
|
&self.appearance_panel,
|
||||||
|
interactive_input.pointer_pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InteractiveEventKind::PointerPrimaryButtonDragStarted => {
|
||||||
|
self.overlay.drag_start(
|
||||||
|
board,
|
||||||
|
&self.appearance_panel,
|
||||||
|
interactive_input.pointer_pos,
|
||||||
|
interactive_event.ctrl,
|
||||||
|
interactive_event.shift,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
InteractiveEventKind::PointerPrimaryButtonDragStopped => {
|
||||||
|
self.overlay.drag_stop(
|
||||||
|
board,
|
||||||
|
&self.appearance_panel,
|
||||||
|
interactive_input.pointer_pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,25 @@ pub struct InteractiveInput {
|
||||||
pub dt: f32,
|
pub dt: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An event received from the user
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum InteractiveEvent {
|
pub enum InteractiveEventKind {
|
||||||
PointerPrimaryButtonClicked,
|
PointerPrimaryButtonClicked,
|
||||||
|
PointerPrimaryButtonDragStarted,
|
||||||
|
PointerPrimaryButtonDragStopped,
|
||||||
PointerSecondaryButtonClicked,
|
PointerSecondaryButtonClicked,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An event received from the user
|
||||||
|
pub struct InteractiveEvent {
|
||||||
|
pub kind: InteractiveEventKind,
|
||||||
|
|
||||||
|
/// `true` if the `Ctrl` key pressed during the event
|
||||||
|
pub ctrl: bool,
|
||||||
|
|
||||||
|
/// `true` if the `Shift` key pressed during the event
|
||||||
|
pub shift: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// This is the execution context passed to the stepper on each step
|
/// This is the execution context passed to the stepper on each step
|
||||||
pub struct ActivityContext<'a, M> {
|
pub struct ActivityContext<'a, M> {
|
||||||
pub interactive_input: &'a InteractiveInput,
|
pub interactive_input: &'a InteractiveInput,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
activity::{ActivityContext, InteractiveEvent},
|
activity::{ActivityContext, InteractiveEvent, InteractiveEventKind},
|
||||||
interaction::InteractionError,
|
interaction::InteractionError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -128,8 +128,8 @@ impl<M: AccessMesadata> OnEvent<ActivityContext<'_, M>, InteractiveEvent> for Ro
|
||||||
context: &mut ActivityContext<M>,
|
context: &mut ActivityContext<M>,
|
||||||
event: InteractiveEvent,
|
event: InteractiveEvent,
|
||||||
) -> Result<(), InteractionError> {
|
) -> Result<(), InteractionError> {
|
||||||
match event {
|
match event.kind {
|
||||||
InteractiveEvent::PointerPrimaryButtonClicked if self.end_pin.is_none() => {
|
InteractiveEventKind::PointerPrimaryButtonClicked if self.end_pin.is_none() => {
|
||||||
if let Some((layer, idx, pos)) = try_select_pin(context) {
|
if let Some((layer, idx, pos)) = try_select_pin(context) {
|
||||||
// make sure double-click or such doesn't corrupt state
|
// make sure double-click or such doesn't corrupt state
|
||||||
if let Some(start_pin) = self.start_pin {
|
if let Some(start_pin) = self.start_pin {
|
||||||
|
|
@ -151,7 +151,7 @@ impl<M: AccessMesadata> OnEvent<ActivityContext<'_, M>, InteractiveEvent> for Ro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InteractiveEvent::PointerSecondaryButtonClicked => {
|
InteractiveEventKind::PointerSecondaryButtonClicked => {
|
||||||
if self.end_pin.is_some() {
|
if self.end_pin.is_some() {
|
||||||
log::debug!("un-clicked end pin");
|
log::debug!("un-clicked end pin");
|
||||||
self.end_pin = None;
|
self.end_pin = None;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue