mirror of https://codeberg.org/topola/topola.git
egui: move the top panel to its own file
This commit is contained in:
parent
5067fb55d9
commit
ce1a070a70
|
|
@ -40,10 +40,10 @@ use topola::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{layers::Layers, overlay::Overlay, painter::Painter};
|
use crate::{layers::Layers, overlay::Overlay, painter::Painter, top::Top};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct SharedData {
|
pub struct SharedData {
|
||||||
pub from: Option<FixedDotIndex>,
|
pub from: Option<FixedDotIndex>,
|
||||||
pub to: Option<FixedDotIndex>,
|
pub to: Option<FixedDotIndex>,
|
||||||
pub navmesh: Option<Navmesh>,
|
pub navmesh: Option<Navmesh>,
|
||||||
|
|
@ -72,10 +72,7 @@ pub struct App {
|
||||||
from_rect: egui::emath::Rect,
|
from_rect: egui::emath::Rect,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
is_placing_via: bool,
|
top: Top,
|
||||||
|
|
||||||
#[serde(skip)]
|
|
||||||
show_ratsnest: bool,
|
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
layers: Option<Layers>,
|
layers: Option<Layers>,
|
||||||
|
|
@ -89,8 +86,7 @@ impl Default for App {
|
||||||
shared_data: Default::default(),
|
shared_data: Default::default(),
|
||||||
text_channel: channel(),
|
text_channel: channel(),
|
||||||
from_rect: egui::Rect::from_x_y_ranges(0.0..=1000000.0, 0.0..=500000.0),
|
from_rect: egui::Rect::from_x_y_ranges(0.0..=1000000.0, 0.0..=500000.0),
|
||||||
is_placing_via: false,
|
top: Top::new(),
|
||||||
show_ratsnest: true,
|
|
||||||
layers: None,
|
layers: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,8 +104,8 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DebugRouterObserver {
|
pub struct DebugRouterObserver {
|
||||||
shared_data: Arc<Mutex<SharedData>>,
|
pub shared_data: Arc<Mutex<SharedData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RulesTrait + std::fmt::Debug> RouterObserverTrait<R> for DebugRouterObserver {
|
impl<R: RulesTrait + std::fmt::Debug> RouterObserverTrait<R> for DebugRouterObserver {
|
||||||
|
|
@ -166,8 +162,6 @@ impl eframe::App for App {
|
||||||
|
|
||||||
/// Called each time the UI has to be repainted.
|
/// Called each time the UI has to be repainted.
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
let mut has_acted = false;
|
|
||||||
|
|
||||||
if cfg!(target_arch = "wasm32") {
|
if cfg!(target_arch = "wasm32") {
|
||||||
if let Ok(file_contents) = self.text_channel.1.try_recv() {
|
if let Ok(file_contents) = self.text_channel.1.try_recv() {
|
||||||
let design = DsnDesign::load_from_string(file_contents).unwrap();
|
let design = DsnDesign::load_from_string(file_contents).unwrap();
|
||||||
|
|
@ -190,159 +184,13 @@ impl eframe::App for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
self.top.update(
|
||||||
egui::menu::bar(ui, |ui| {
|
ctx,
|
||||||
ui.menu_button("File", |ui| {
|
self.shared_data.clone(),
|
||||||
if ui.button("Open").clicked() {
|
self.text_channel.0.clone(),
|
||||||
// `Context` is cheap to clone as it's wrapped in an `Arc`.
|
&self.invoker,
|
||||||
let ctx = ui.ctx().clone();
|
&self.overlay,
|
||||||
// NOTE: On Linux, this requires Zenity to be installed on your system.
|
);
|
||||||
let sender = self.text_channel.0.clone();
|
|
||||||
let task = rfd::AsyncFileDialog::new().pick_file();
|
|
||||||
|
|
||||||
execute(async move {
|
|
||||||
if let Some(file_handle) = task.await {
|
|
||||||
sender.send(channel_text(file_handle).await);
|
|
||||||
ctx.request_repaint();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
if ui.button("Load history").clicked() {
|
|
||||||
if let Some(invoker_arc_mutex) = &self.invoker {
|
|
||||||
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
|
||||||
let ctx = ui.ctx().clone();
|
|
||||||
let task = rfd::AsyncFileDialog::new().pick_file();
|
|
||||||
|
|
||||||
execute(async move {
|
|
||||||
if let Some(file_handle) = task.await {
|
|
||||||
let path = file_handle.path();
|
|
||||||
let mut invoker = invoker_arc_mutex.lock().unwrap();
|
|
||||||
let mut file = File::open(path).unwrap();
|
|
||||||
invoker.replay(serde_json::from_reader(file).unwrap());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.button("Save history").clicked() {
|
|
||||||
if let Some(invoker_arc_mutex) = &self.invoker {
|
|
||||||
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
|
||||||
let ctx = ui.ctx().clone();
|
|
||||||
let task = rfd::AsyncFileDialog::new().save_file();
|
|
||||||
|
|
||||||
execute(async move {
|
|
||||||
if let Some(file_handle) = task.await {
|
|
||||||
let path = file_handle.path();
|
|
||||||
let mut invoker = invoker_arc_mutex.lock().unwrap();
|
|
||||||
let mut file = File::create(path).unwrap();
|
|
||||||
serde_json::to_writer_pretty(file, invoker.history());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
// "Quit" button wouldn't work on a Web page.
|
|
||||||
if !cfg!(target_arch = "wasm32") {
|
|
||||||
if ui.button("Quit").clicked() {
|
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
if ui.button("Autoroute").clicked() {
|
|
||||||
if let (Some(invoker_arc_mutex), Some(overlay)) = (&self.invoker, &self.overlay)
|
|
||||||
{
|
|
||||||
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
|
||||||
let shared_data_arc_mutex = self.shared_data.clone();
|
|
||||||
let selection = overlay.selection().clone();
|
|
||||||
|
|
||||||
execute(async move {
|
|
||||||
let mut invoker = invoker_arc_mutex.lock().unwrap();
|
|
||||||
let mut execute = invoker.execute_walk(Command::Autoroute(selection));
|
|
||||||
|
|
||||||
if let Execute::Autoroute(ref mut autoroute) = execute {
|
|
||||||
let from = autoroute.navmesh().as_ref().unwrap().source();
|
|
||||||
let to = autoroute.navmesh().as_ref().unwrap().target();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut shared_data = shared_data_arc_mutex.lock().unwrap();
|
|
||||||
shared_data.from = Some(from);
|
|
||||||
shared_data.to = Some(to);
|
|
||||||
shared_data.navmesh = autoroute.navmesh().clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = loop {
|
|
||||||
let status = match execute.step(
|
|
||||||
&mut invoker,
|
|
||||||
&mut DebugRouterObserver {
|
|
||||||
shared_data: shared_data_arc_mutex.clone(),
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Ok(status) => status,
|
|
||||||
Err(err) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Execute::Autoroute(ref mut autoroute) = execute {
|
|
||||||
shared_data_arc_mutex.lock().unwrap().navmesh =
|
|
||||||
autoroute.navmesh().clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let InvokerStatus::Finished = status {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui
|
|
||||||
.toggle_value(&mut self.is_placing_via, "Place Via")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
has_acted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
if ui.button("Undo").clicked()
|
|
||||||
|| ctx.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::Z))
|
|
||||||
{
|
|
||||||
if let Some(invoker_arc_mutex) = &self.invoker {
|
|
||||||
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
|
||||||
execute(async move {
|
|
||||||
invoker_arc_mutex.lock().unwrap().undo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.button("Redo").clicked()
|
|
||||||
|| ctx.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::Y))
|
|
||||||
{
|
|
||||||
if let Some(invoker_arc_mutex) = &self.invoker {
|
|
||||||
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
|
||||||
execute(async move {
|
|
||||||
invoker_arc_mutex.lock().unwrap().redo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
ui.toggle_value(&mut self.show_ratsnest, "Show Ratsnest");
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(ref mut layers) = self.layers {
|
if let Some(ref mut layers) = self.layers {
|
||||||
if let Some(invoker_arc_mutex) = &self.invoker {
|
if let Some(invoker_arc_mutex) = &self.invoker {
|
||||||
|
|
@ -383,32 +231,30 @@ impl eframe::App for App {
|
||||||
|
|
||||||
if let Some(invoker_arc_mutex) = &self.invoker {
|
if let Some(invoker_arc_mutex) = &self.invoker {
|
||||||
if ctx.input(|i| i.pointer.any_click()) {
|
if ctx.input(|i| i.pointer.any_click()) {
|
||||||
if !has_acted {
|
if self.top.is_placing_via {
|
||||||
if self.is_placing_via {
|
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
||||||
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
|
||||||
|
|
||||||
execute(async move {
|
execute(async move {
|
||||||
let mut invoker = invoker_arc_mutex.lock().unwrap();
|
let mut invoker = invoker_arc_mutex.lock().unwrap();
|
||||||
invoker.execute(
|
invoker.execute(
|
||||||
Command::PlaceVia(ViaWeight {
|
Command::PlaceVia(ViaWeight {
|
||||||
from_layer: 0,
|
from_layer: 0,
|
||||||
to_layer: 0,
|
to_layer: 0,
|
||||||
circle: Circle {
|
circle: Circle {
|
||||||
pos: point! {x: latest_pos.x as f64, y: -latest_pos.y as f64},
|
pos: point! {x: latest_pos.x as f64, y: -latest_pos.y as f64},
|
||||||
r: 10000.0,
|
r: 10000.0,
|
||||||
},
|
},
|
||||||
maybe_net: Some(1234),
|
maybe_net: Some(1234),
|
||||||
}),
|
}),
|
||||||
&mut EmptyRouterObserver,
|
&mut EmptyRouterObserver,
|
||||||
);
|
|
||||||
});
|
|
||||||
} else if let Some(overlay) = &mut self.overlay {
|
|
||||||
let invoker = invoker_arc_mutex.lock().unwrap();
|
|
||||||
overlay.click(
|
|
||||||
invoker.autorouter().board(),
|
|
||||||
point! {x: latest_pos.x as f64, y: -latest_pos.y as f64},
|
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
} else if let Some(overlay) = &mut self.overlay {
|
||||||
|
let invoker = invoker_arc_mutex.lock().unwrap();
|
||||||
|
overlay.click(
|
||||||
|
invoker.autorouter().board(),
|
||||||
|
point! {x: latest_pos.x as f64, y: -latest_pos.y as f64},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -473,7 +319,7 @@ impl eframe::App for App {
|
||||||
painter.paint_polygon(&board.layout().zone(zone).shape().polygon, color)
|
painter.paint_polygon(&board.layout().zone(zone).shape().polygon, color)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.show_ratsnest {
|
if self.top.show_ratsnest {
|
||||||
for edge in overlay.ratsnest().graph().edge_references() {
|
for edge in overlay.ratsnest().graph().edge_references() {
|
||||||
let from = overlay
|
let from = overlay
|
||||||
.ratsnest()
|
.ratsnest()
|
||||||
|
|
@ -571,22 +417,22 @@ impl eframe::App for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn execute<F: Future<Output = ()> + Send + 'static>(f: F) {
|
pub fn execute<F: Future<Output = ()> + Send + 'static>(f: F) {
|
||||||
std::thread::spawn(move || futures::executor::block_on(f));
|
std::thread::spawn(move || futures::executor::block_on(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
fn execute<F: Future<Output = ()> + 'static>(f: F) {
|
pub fn execute<F: Future<Output = ()> + 'static>(f: F) {
|
||||||
wasm_bindgen_futures::spawn_local(f);
|
wasm_bindgen_futures::spawn_local(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
async fn channel_text(file_handle: rfd::FileHandle) -> String {
|
pub async fn channel_text(file_handle: rfd::FileHandle) -> String {
|
||||||
file_handle.path().to_str().unwrap().to_string()
|
file_handle.path().to_str().unwrap().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
async fn channel_text(file_handle: rfd::FileHandle) -> String {
|
pub async fn channel_text(file_handle: rfd::FileHandle) -> String {
|
||||||
std::str::from_utf8(&file_handle.read().await)
|
std::str::from_utf8(&file_handle.read().await)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string()
|
.to_string()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ mod app;
|
||||||
mod layers;
|
mod layers;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
mod painter;
|
mod painter;
|
||||||
|
mod top;
|
||||||
use app::App;
|
use app::App;
|
||||||
|
|
||||||
// Build to native.
|
// Build to native.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
sync::{mpsc::Sender, Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use topola::{
|
||||||
|
autorouter::invoker::{Command, Execute, Invoker, InvokerStatus},
|
||||||
|
dsn::mesadata::DsnMesadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::{channel_text, execute, DebugRouterObserver, SharedData},
|
||||||
|
overlay::Overlay,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Top {
|
||||||
|
pub is_placing_via: bool,
|
||||||
|
pub show_ratsnest: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Top {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
is_placing_via: false,
|
||||||
|
show_ratsnest: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
ctx: &egui::Context,
|
||||||
|
shared_data: Arc<Mutex<SharedData>>,
|
||||||
|
sender: Sender<String>,
|
||||||
|
maybe_invoker: &Option<Arc<Mutex<Invoker<DsnMesadata>>>>,
|
||||||
|
maybe_overlay: &Option<Overlay>,
|
||||||
|
) {
|
||||||
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
|
egui::menu::bar(ui, |ui| {
|
||||||
|
ui.menu_button("File", |ui| {
|
||||||
|
if ui.button("Open").clicked() {
|
||||||
|
// `Context` is cheap to clone as it's wrapped in an `Arc`.
|
||||||
|
let ctx = ui.ctx().clone();
|
||||||
|
// NOTE: On Linux, this requires Zenity to be installed on your system.
|
||||||
|
let task = rfd::AsyncFileDialog::new().pick_file();
|
||||||
|
|
||||||
|
execute(async move {
|
||||||
|
if let Some(file_handle) = task.await {
|
||||||
|
sender.send(channel_text(file_handle).await);
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button("Load history").clicked() {
|
||||||
|
if let Some(invoker_arc_mutex) = &maybe_invoker {
|
||||||
|
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
||||||
|
let ctx = ui.ctx().clone();
|
||||||
|
let task = rfd::AsyncFileDialog::new().pick_file();
|
||||||
|
|
||||||
|
execute(async move {
|
||||||
|
if let Some(file_handle) = task.await {
|
||||||
|
let path = file_handle.path();
|
||||||
|
let mut invoker = invoker_arc_mutex.lock().unwrap();
|
||||||
|
let mut file = File::open(path).unwrap();
|
||||||
|
invoker.replay(serde_json::from_reader(file).unwrap());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Save history").clicked() {
|
||||||
|
if let Some(invoker_arc_mutex) = &maybe_invoker {
|
||||||
|
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
||||||
|
let ctx = ui.ctx().clone();
|
||||||
|
let task = rfd::AsyncFileDialog::new().save_file();
|
||||||
|
|
||||||
|
execute(async move {
|
||||||
|
if let Some(file_handle) = task.await {
|
||||||
|
let path = file_handle.path();
|
||||||
|
let mut invoker = invoker_arc_mutex.lock().unwrap();
|
||||||
|
let mut file = File::create(path).unwrap();
|
||||||
|
serde_json::to_writer_pretty(file, invoker.history());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// "Quit" button wouldn't work on a Web page.
|
||||||
|
if !cfg!(target_arch = "wasm32") {
|
||||||
|
if ui.button("Quit").clicked() {
|
||||||
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button("Autoroute").clicked() {
|
||||||
|
if let (Some(invoker_arc_mutex), Some(overlay)) =
|
||||||
|
(&maybe_invoker, &maybe_overlay)
|
||||||
|
{
|
||||||
|
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
||||||
|
let shared_data_arc_mutex = shared_data.clone();
|
||||||
|
let selection = overlay.selection().clone();
|
||||||
|
|
||||||
|
execute(async move {
|
||||||
|
let mut invoker = invoker_arc_mutex.lock().unwrap();
|
||||||
|
let mut execute = invoker.execute_walk(Command::Autoroute(selection));
|
||||||
|
|
||||||
|
if let Execute::Autoroute(ref mut autoroute) = execute {
|
||||||
|
let from = autoroute.navmesh().as_ref().unwrap().source();
|
||||||
|
let to = autoroute.navmesh().as_ref().unwrap().target();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut shared_data = shared_data_arc_mutex.lock().unwrap();
|
||||||
|
shared_data.from = Some(from);
|
||||||
|
shared_data.to = Some(to);
|
||||||
|
shared_data.navmesh = autoroute.navmesh().clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = loop {
|
||||||
|
let status = match execute.step(
|
||||||
|
&mut invoker,
|
||||||
|
&mut DebugRouterObserver {
|
||||||
|
shared_data: shared_data_arc_mutex.clone(),
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(status) => status,
|
||||||
|
Err(err) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Execute::Autoroute(ref mut autoroute) = execute {
|
||||||
|
shared_data_arc_mutex.lock().unwrap().navmesh =
|
||||||
|
autoroute.navmesh().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let InvokerStatus::Finished = status {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.toggle_value(&mut self.is_placing_via, "Place Via");
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button("Undo").clicked()
|
||||||
|
|| ctx.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::Z))
|
||||||
|
{
|
||||||
|
if let Some(invoker_arc_mutex) = &maybe_invoker {
|
||||||
|
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
||||||
|
execute(async move {
|
||||||
|
invoker_arc_mutex.lock().unwrap().undo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Redo").clicked()
|
||||||
|
|| ctx.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::Y))
|
||||||
|
{
|
||||||
|
if let Some(invoker_arc_mutex) = &maybe_invoker {
|
||||||
|
let invoker_arc_mutex = invoker_arc_mutex.clone();
|
||||||
|
execute(async move {
|
||||||
|
invoker_arc_mutex.lock().unwrap().redo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.toggle_value(&mut self.show_ratsnest, "Show Ratsnest");
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue