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:
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,6 +200,64 @@ impl DebuggerUi {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toolbar(&mut self, ui: &mut Ui) {
|
||||||
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
|
|
||||||
|
let save_enabled = self.central_tiles.active_tile()
|
||||||
|
.and_then(|id| self.central_tiles.pane_mut::<Editor>(id))
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
ui.set_height(30.0);
|
||||||
|
ui.menu_button("File", |ui| {
|
||||||
|
|
||||||
|
if ui.button("Open").clicked() {
|
||||||
|
self.file_dialog.open_pick(Action::OpenFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Load Binary").clicked() {
|
||||||
|
self.file_dialog.open_pick(Action::LoadBinary);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() {
|
||||||
|
if let Some(active) = self.central_tiles.active_tile() {
|
||||||
|
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
||||||
|
match editor.filename().cloned() {
|
||||||
|
Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(),
|
||||||
|
None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() {
|
||||||
|
if let Some(active) = self.central_tiles.active_tile() {
|
||||||
|
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
||||||
|
self.file_dialog.open_save(move |path| Action::SaveFile(path, active))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Exit").clicked() {
|
||||||
|
self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.menu_button("Tools", |ui| {
|
||||||
|
for PanelFactory(name, constructor) in &self.tools {
|
||||||
|
if ui.button(name).clicked() {
|
||||||
|
self.central_tiles.add_dyn_tab(constructor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.menu_button("Help", |ui| {
|
||||||
|
if ui.button("About").clicked() {
|
||||||
|
self.central_tiles.add_tab(AboutScreen);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for DebuggerUi {
|
impl eframe::App for DebuggerUi {
|
||||||
@@ -190,70 +274,23 @@ impl eframe::App for DebuggerUi {
|
|||||||
|
|
||||||
self.error_dialog.show(ui);
|
self.error_dialog.show(ui);
|
||||||
|
|
||||||
egui::Panel::top("toolbar").resizable(false).show(ui, |ui| {
|
Panel::top("menu_bar").resizable(false).show(ui, |ui| {
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
self.toolbar(ui);
|
||||||
|
|
||||||
let save_enabled = self.central_tiles.active_tile()
|
|
||||||
.and_then(|id| self.central_tiles.pane_mut::<Editor>(id))
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
ui.set_height(30.0);
|
|
||||||
ui.menu_button("File", |ui| {
|
|
||||||
|
|
||||||
if ui.button("Open").clicked() {
|
|
||||||
self.file_dialog.open_pick(Action::OpenFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.button("Load Binary").clicked() {
|
|
||||||
self.file_dialog.open_pick(Action::LoadBinary);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() {
|
|
||||||
if let Some(active) = self.central_tiles.active_tile() {
|
|
||||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
|
||||||
match editor.filename().cloned() {
|
|
||||||
Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(),
|
|
||||||
None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() {
|
|
||||||
if let Some(active) = self.central_tiles.active_tile() {
|
|
||||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
|
||||||
self.file_dialog.open_save(move |path| Action::SaveFile(path, active))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.button("Exit").clicked() {
|
|
||||||
self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.menu_button("Tools", |ui| {
|
|
||||||
for PanelFactory(name, constructor) in &self.tools {
|
|
||||||
if ui.button(name).clicked() {
|
|
||||||
self.central_tiles.add_dyn_tab(constructor());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.menu_button("Help", |ui| {
|
|
||||||
if ui.button("About").clicked() {
|
|
||||||
self.central_tiles.add_tab(AboutScreen);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
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`.
|
||||||
|
|||||||
Reference in New Issue
Block a user