added close-confirmation for editor. TODO: make this behaviour dependent on the current file being saved (buffer is modified etc. maybe use a hash for memory efficiency)

This commit is contained in:
2026-07-22 00:10:42 +01:00
parent adfba876d2
commit acf42d56f2
4 changed files with 152 additions and 84 deletions
@@ -1,4 +1,5 @@
use egui::{Modal, Id, Layout, Align, RichText}; use egui::{Modal, Id, Layout, Align, RichText};
use crate::ui::debugger::ui_helpers::PaddedWidget;
pub struct ConfirmDialog<T> { pub struct ConfirmDialog<T> {
open: bool, open: bool,
@@ -46,11 +47,13 @@ impl<T> ConfirmDialog<T> {
ui.separator(); ui.separator();
ui.with_layout(Layout::right_to_left(Align::Center), |ui| { ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if ui.button(RichText::new("Confirm").strong()).clicked() {
if PaddedWidget::button("Confirm").ui(ui).clicked() {
result = Some(true); result = Some(true);
} }
ui.add_space(6.0); ui.add_space(6.0);
if ui.button("Cancel").clicked() { if PaddedWidget::button("Cancel").ui(ui).clicked() {
result = Some(false); result = Some(false);
} }
}); });
+60 -23
View File
@@ -1,8 +1,10 @@
use std::fmt::Debug;
use std::fs::File; use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use egui::{CentralPanel, Panel, Ui};
use egui_tiles::TileId; use egui_tiles::TileId;
use common::formats::binary_dse::DseExecutable; use common::formats::binary_dse::DseExecutable;
use emulator_core::memory::ram::MemoryBank; use emulator_core::memory::ram::MemoryBank;
@@ -30,10 +32,23 @@ pub mod dialog;
pub mod panels; pub mod panels;
pub mod ui_helpers; pub mod ui_helpers;
trait FileFormat: Debug + Clone {
fn extension(&self) -> &'static str;
}
impl FileFormat for DseExecutable {
fn extension(&self) -> &'static str {
"dse"
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Action { pub enum Action {
RequestCloseTab(TileId),
CloseTab(TileId),
Quit, Quit,
OpenFile(PathBuf), OpenFile(PathBuf),
// OpenCachedFile(Box<dyn FileFormat>),
LoadBinary(PathBuf), LoadBinary(PathBuf),
SaveFile(PathBuf, TileId), SaveFile(PathBuf, TileId),
RequestSaveAs(TileId), RequestSaveAs(TileId),
@@ -125,12 +140,25 @@ impl DebuggerUi {
pub fn perform_action(&mut self, action: Action) { pub fn perform_action(&mut self, action: Action) {
match action { match action {
Action::RequestCloseTab(tile_id) => {
self.confirm_dialog.open(
"Close Tab",
"Closing this tab may result in losing unsaved work",
Action::CloseTab(tile_id)
);
}
Action::CloseTab(tile_id) => {
self.central_tiles.close_tab(tile_id);
}
Action::Quit => std::process::exit(0), Action::Quit => std::process::exit(0),
// Opens a file (usually constructed by the file dialogue) // Opens a file (usually constructed by the file dialogue)
Action::OpenFile(path) => { Action::OpenFile(path) => {
match path.extension().map(|s| s.to_str()).flatten() { match path.extension().map(|s| s.to_str()).flatten() {
Some("dsa") => { self.central_tiles.add_tab(Editor::new_open(&path)); }, Some("dsa" | "dsc") => { self.central_tiles.add_tab(Editor::new_open(&path)); },
Some("dsb") => { self.central_tiles.add_tab(Disassembler::new_open(&path)); }, Some("dsb") => { self.central_tiles.add_tab(Disassembler::new_open(&path)); },
Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); }, Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); },
Some(_) => todo!(), Some(_) => todo!(),
@@ -160,8 +188,6 @@ impl DebuggerUi {
} }
Action::SaveFile(path, tile) => { Action::SaveFile(path, tile) => {
println!("ACTION: saving file {} from tile {tile:?}", path.display());
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(tile) { if let Some(editor) = self.central_tiles.pane_mut::<Editor>(tile) {
if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) { if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) {
self.perform_action(Action::ShowError(e.to_string())) self.perform_action(Action::ShowError(e.to_string()))
@@ -174,23 +200,8 @@ impl DebuggerUi {
} }
}; };
} }
}
impl eframe::App for DebuggerUi { fn toolbar(&mut self, ui: &mut Ui) {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
ui.request_repaint_after(Duration::from_millis(16));
if let Some(action) = self.confirm_dialog.show(ui) {
self.perform_action(action);
}
if let Some(action) = self.file_dialog.show(ui) {
self.perform_action(action);
}
self.error_dialog.show(ui);
egui::Panel::top("toolbar").resizable(false).show(ui, |ui| {
egui::MenuBar::new().ui(ui, |ui| { egui::MenuBar::new().ui(ui, |ui| {
let save_enabled = self.central_tiles.active_tile() let save_enabled = self.central_tiles.active_tile()
@@ -246,14 +257,40 @@ impl eframe::App for DebuggerUi {
} }
}); });
}); });
}
}
impl eframe::App for DebuggerUi {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
ui.request_repaint_after(Duration::from_millis(16));
if let Some(action) = self.confirm_dialog.show(ui) {
self.perform_action(action);
}
if let Some(action) = self.file_dialog.show(ui) {
self.perform_action(action);
}
self.error_dialog.show(ui);
Panel::top("menu_bar").resizable(false).show(ui, |ui| {
self.toolbar(ui);
}); });
egui::Panel::top("info_panel").resizable(false).show(ui, |ui| {
Panel::top("control_panel").resizable(false).show(ui, |ui| {
self.controller.ui(ui); self.controller.ui(ui);
}); });
egui::Panel::left("left_panel").resizable(false).show(ui, |ui| {});
egui::Panel::right("right_panel").resizable(false).show(ui, |ui| {}); Panel::left("left_panel").resizable(false).show(ui, |ui| {
egui::CentralPanel::default().show(ui, |ui| { // TODO: File Explorer!
});
// Panel::right("right_panel").resizable(false).show(ui, |ui| {
// // TODO: not sure what to put here!
// });
CentralPanel::default().show(ui, |ui| {
if let Some(action) = self.central_tiles.ui(ui) { if let Some(action) = self.central_tiles.ui(ui) {
self.perform_action(action); self.perform_action(action);
} }
@@ -86,4 +86,8 @@ impl TilePane for Editor {
} }
fn as_any_mut(&mut self) -> &mut dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self }
fn close_requires_confirmation(&mut self, _tile_id: TileId) -> bool {
true
}
} }
@@ -6,19 +6,31 @@ use std::any::Any;
use egui::{Id, Response, Stroke, StrokeKind, Ui, Widget}; use egui::{Id, Response, Stroke, StrokeKind, Ui, Widget};
use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse}; use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse};
use crate::ui::debugger::Action; use crate::ui::debugger::Action;
use crate::ui::debugger::Action::RequestCloseTab;
/// Anything that can live inside a tab in the central tiled area. /// Anything that can live inside a tab in the central tiled area.
/// `as_any_mut` lets callers reach back into a specific pane's concrete /// `as_any_mut` lets callers reach back into a specific pane's concrete
/// type after it's been handed off to the tree (e.g. to write into the /// type after it's been handed off to the tree (e.g. to write into the
/// editor's buffer from an `Action` handler). /// editor's buffer from an `Action` handler).
pub trait TilePane: Any { pub trait TilePane: Any {
/// the title that's shown on the tab in the UI.
fn title(&self) -> egui::WidgetText; fn title(&self) -> egui::WidgetText;
/// main UI function. returns an action in order to communicate with the parent UI
fn ui(&mut self, ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action>; fn ui(&mut self, ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action>;
/// required in order to be used with a pane
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
/// the menu options exposed by the tile. these inform the main application menu based on the current focused tile
fn menu(&mut self, _ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action> { fn menu(&mut self, _ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action> {
None None
} }
fn close_requires_confirmation(&mut self, _tile_id: TileId) -> bool {
false
}
} }
type Pane = Box<dyn TilePane>; type Pane = Box<dyn TilePane>;
@@ -29,24 +41,6 @@ struct TileBehavior {
} }
impl Behavior<Pane> for TileBehavior { impl Behavior<Pane> for TileBehavior {
fn is_tab_closable(&self, _tiles: &Tiles<Pane>, _tile_id: TileId) -> bool {
true
}
fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool {
tiles.remove(tile_id);
true
}
fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
36.0 // taller tab row = more vertical padding around the label
}
fn tab_bg_color(&self, visuals: &egui::Visuals, _tiles: &egui_tiles::Tiles<Box<dyn TilePane>>, _tile_id: egui_tiles::TileId, tab_state: &egui_tiles::TabState) -> egui::Color32 {
visuals.widgets.inactive.bg_fill
}
fn pane_ui( fn pane_ui(
&mut self, &mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
@@ -65,11 +59,34 @@ impl Behavior<Pane> for TileBehavior {
UiResponse::None UiResponse::None
} }
fn tab_title_for_pane(&mut self, pane: &Pane) -> egui::WidgetText { fn tab_title_for_pane(&mut self, pane: &Pane) -> egui::WidgetText {
pane.title() pane.title()
} }
fn is_tab_closable(&self, _tiles: &Tiles<Pane>, _tile_id: TileId) -> bool {
true
}
fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool {
let Some(Tile::Pane(pane)) = tiles.get_mut(tile_id) else {
return false
};
if pane.close_requires_confirmation(tile_id) {
self.pending_action = Some(RequestCloseTab(tile_id));
return false;
}
tiles.remove(tile_id);
true
}
fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
36.0 // taller tab row = more vertical padding around the label
}
fn simplification_options(&self) -> SimplificationOptions { fn simplification_options(&self) -> SimplificationOptions {
SimplificationOptions { SimplificationOptions {
// Keep the tab bar around even with only one tab, and don't let // Keep the tab bar around even with only one tab, and don't let
@@ -78,6 +95,10 @@ impl Behavior<Pane> for TileBehavior {
..Default::default() ..Default::default()
} }
} }
fn tab_bg_color(&self, visuals: &egui::Visuals, _tiles: &Tiles<Box<dyn TilePane>>, _tile_id: TileId, tab_state: &TabState) -> egui::Color32 {
visuals.widgets.inactive.bg_fill
}
} }
/// A single top-level tab strip that tools can add tabs to at runtime. /// A single top-level tab strip that tools can add tabs to at runtime.
@@ -111,6 +132,9 @@ impl CentralTiles {
self.add_dyn_tab(Box::new(pane)) self.add_dyn_tab(Box::new(pane))
} }
pub fn close_tab(&mut self, tile_id: TileId) {
self.tree.tiles.remove(tile_id);
}
/// Add a new tab and make it active. Returns the `TileId` so the /// Add a new tab and make it active. Returns the `TileId` so the
/// caller can hold onto it for later access via `pane_mut`. /// caller can hold onto it for later access via `pane_mut`.