wrote new "ContextTree" system, emulator filetree is kind of broken for now. next commit will fix this.

This commit is contained in:
2026-08-03 01:45:30 +01:00
parent e6ae60a798
commit 8d28bf39f8
10 changed files with 376 additions and 204 deletions
+2 -4
View File
@@ -3,13 +3,12 @@ use egui_tiles::TileId;
use std::fmt::Debug;
use std::path::PathBuf;
/// the action enum forms the core of the application's state.
/// - Actions are used as the main way that Tiles inside the application pass state updates back to the parent UI,
/// specifically when those state updates involve other files. for example, pressing "build" in the editor would emit
/// a SpawnProcess action, running some task which on completion emits a LoadBinary action on the result
#[derive(Debug)]
pub enum Action {
None,
RequestCloseTab(TileId),
CloseTab(TileId),
Quit,
@@ -18,7 +17,7 @@ pub enum Action {
SaveFile(PathBuf, TileId),
RequestSaveAs(TileId),
ShowError(String),
SpawnProcess(ProcessSpawner)
SpawnProcess(ProcessSpawner),
}
#[derive(Debug, Clone)]
@@ -26,4 +25,3 @@ pub enum DataSource {
File(PathBuf),
Data(Vec<u8>),
}
+19 -16
View File
@@ -17,13 +17,12 @@ use egui_tiles::TileId;
use process_manager::ProcessManager;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::time::Duration;
use tiles::PanelFactory;
use tiles::documentation::Documentation;
use tiles::editor::Editor;
use tiles::io_devices::framebuffer::FrameBufferUi;
use zxq5_egui::file_tree::{FileTree, FileTreeEvent};
use zxq5_egui::context_tree::{ContextTree, NewFileTree};
pub mod appearance;
mod controller;
@@ -45,7 +44,7 @@ pub struct DamnSimpleUI {
show_process_manager: bool,
process_manager: ProcessManager,
file_tree: FileTree,
file_tree: ContextTree<Action, NewFileTree>,
}
impl eframe::App for DamnSimpleUI {
@@ -75,17 +74,13 @@ impl eframe::App for DamnSimpleUI {
});
Panel::left("left_panel").resizable(true).show(ui, |ui| {
let inner_res = self.file_tree.ui(ui, &mut |ui: &mut Ui, path: &Path| {
ui.button("Load Binary")
.clicked()
.then_some(Action::LoadBinary(DataSource::File(path.to_path_buf())))
});
let inner_res = self.file_tree.ui(ui);
if let Some(event) = inner_res.inner {
match event {
FileTreeEvent::Clicked(path) => self.perform_action(Action::OpenFile(path)),
FileTreeEvent::ContextAction(action) => self.perform_action(action),
}
// match event {
// ContextTreeEvent::Clicked(node) => self.perform_action(Action::OpenFile(node)),
// ContextTreeEvent::ContextAction(action, node) => self.perform_action(action),
// }
}
});
@@ -151,15 +146,23 @@ impl DamnSimpleUI {
emulator,
show_process_manager: true,
process_manager: ProcessManager::new(),
file_tree: FileTree::build(&std::env::current_dir().unwrap()),
file_tree: ContextTree::new(
NewFileTree::build(&std::env::current_dir().unwrap()),
|ui, node| match node {
NewFileTree::Leaf { filename } => ui
.button("Open Binary")
.clicked()
.then_some(Action::LoadBinary(DataSource::File(filename.clone()))),
NewFileTree::Branch { .. } => None,
},
),
}
}
pub fn perform_action(&mut self, action: Action) {
match action {
Action::None => {}
Action::SpawnProcess(spawner) => spawner(&mut self.process_manager),
Action::SpawnProcess(spawner) => spawner.run(&mut self.process_manager),
Action::RequestCloseTab(tile_id) => {
self.confirm_dialog.open(
"Close Tab",
+44 -9
View File
@@ -2,11 +2,32 @@ use crate::state::Action;
use crate::ui::ui_helpers::text::strip_ansi;
use dsa_tasks::{ProcessAgent, TraceableProcess};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::sync::mpsc;
use std::thread::JoinHandle;
use zxq5_egui::tabs::painted_tab;
pub type ProcessSpawner = Box<dyn FnOnce(&mut ProcessManager) + Send>;
pub struct ProcessSpawner(Box<dyn FnOnce(&mut ProcessManager) + Send>);
impl Debug for ProcessSpawner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ProcessSpawner")
}
}
impl ProcessSpawner {
pub(crate) fn run(self, process_manager: &mut ProcessManager) {
self.0(process_manager);
}
pub fn new(func: impl FnOnce(&mut ProcessManager) + Send + 'static) -> Self {
Self(Box::new(func))
}
pub fn into_action(self) -> Action {
Action::SpawnProcess(self)
}
}
pub struct ProcessHandle {
id: usize,
@@ -23,10 +44,10 @@ impl ProcessHandle {
id: usize,
name: impl Into<String>,
process: P,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static,
) -> Self {
let (tx, rx) = mpsc::channel::<String>();
let agent = ProcessAgent::new( tx );
let agent = ProcessAgent::new(tx);
let join_handle = std::thread::spawn(move || {
let result = TraceableProcess::run(Box::new(process), agent);
@@ -34,7 +55,11 @@ impl ProcessHandle {
});
Self {
rx, id, name: name.into(), join_handle: Some(join_handle), logs: Vec::new()
rx,
id,
name: name.into(),
join_handle: Some(join_handle),
logs: Vec::new(),
}
}
}
@@ -63,7 +88,12 @@ impl ProcessManager {
self.terminated_actions.pop_front()
}
pub fn spawn<P: TraceableProcess>(&mut self, name: impl Into<String>, process: P, on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static) {
pub fn spawn<P: TraceableProcess>(
&mut self,
name: impl Into<String>,
process: P,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static,
) {
let handle = ProcessHandle::spawn(self.new_id(), name, process, on_complete);
self.handles.push(handle);
}
@@ -79,7 +109,10 @@ impl ProcessManager {
/// Checks for finished processes and queues their resulting Actions.
fn poll_finished(&mut self) {
for handle in &mut self.handles {
let is_finished = handle.join_handle.as_ref().is_some_and(|jh| jh.is_finished());
let is_finished = handle
.join_handle
.as_ref()
.is_some_and(|jh| jh.is_finished());
if is_finished {
if let Some(jh) = handle.join_handle.take() {
if let Ok(action) = jh.join() {
@@ -102,11 +135,13 @@ impl ProcessManager {
}
// keep active_tab pointing at something valid
if !self.active_tab.map_or(false, |id| self.handles.iter().any(|h| h.id == id)) {
if !self
.active_tab
.map_or(false, |id| self.handles.iter().any(|h| h.id == id))
{
self.active_tab = self.handles.first().map(|h| h.id);
}
let mut to_close = None;
ui.horizontal(|ui| {
@@ -147,4 +182,4 @@ impl ProcessManager {
}
}
}
}
}
+43 -55
View File
@@ -1,5 +1,6 @@
use crate::state::Action;
use crate::state::DataSource::Data;
use crate::ui::process_manager::ProcessSpawner;
use crate::ui::tiles::TilePane;
use crate::ui::tiles::editor::syntax::SyntaxFrom;
use assembler::prelude::Assembler;
@@ -30,7 +31,10 @@ impl Default for Editor {
Self {
buffer: String::new(),
filename: None,
editor: CodeEditor::default().with_fontsize(12.0).with_theme(THEME).with_numlines(true),
editor: CodeEditor::default()
.with_fontsize(12.0)
.with_theme(THEME)
.with_numlines(true),
syntax: Syntax::default(),
completer: Completer::default().with_auto_indent().with_user_words(),
}
@@ -70,106 +74,90 @@ impl Editor {
}
}
pub fn create_build_action(&self, filename: PathBuf, on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static) -> Action {
Action::SpawnProcess(Box::new(move |pm| {
pub fn create_build_action(
&self,
filename: PathBuf,
on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static,
) -> Action {
ProcessSpawner::new(move |pm| {
let builder = Assembler::new(filename);
pm.spawn(
builder.label(),
builder,
move |result| match result {
Ok(bin) => on_complete(bin),
Err(e) => Action::ShowError(e.to_string()),
}
);
}))
pm.spawn(builder.label(), builder, move |result| match result {
Ok(bin) => on_complete(bin),
Err(e) => Action::ShowError(e.to_string()),
});
})
.into_action()
}
}
impl TilePane for Editor {
fn title(&self) -> egui::WidgetText {
self.filename.clone()
self.filename
.clone()
.map(|f| f.file_name().unwrap().to_str().unwrap().into())
.unwrap_or("New File*".into())
}
fn ui(&mut self, ui: &mut Ui, tile_id: TileId) -> Option<Action> {
let mut action = Action::None;
let mut action = None;
egui::Panel::top("panel").show(ui, |ui| {
ui.horizontal(|ui| {
if PaddedWidget::button("Build & Load").ui(ui).clicked() {
let Some(filename) = self.filename.clone() else {
action = Action::ShowError("Error - no filename set".to_string());
action = Some(Action::ShowError("Error - no filename set".to_string()));
return;
};
action = self.create_build_action(
filename, |res| {
Action::LoadBinary(Data(res.to_memory_image()))
}
);
action = Some(self.create_build_action(filename, |res| {
Action::LoadBinary(Data(res.to_memory_image()))
}));
}
if PaddedWidget::button("Build").ui(ui).clicked() {
let Some(filename) = self.filename.clone() else {
action = Action::ShowError("Error - no filename set".to_string());
action = Some(Action::ShowError("Error - no filename set".to_string()));
return;
};
action = self.create_build_action(
filename.clone(), move |res| {
let filename = filename.with_extension("dsb");
let mut writer = File::create(&filename).unwrap();
res.write(&mut writer).unwrap();
Action::OpenFile(filename)
}
);
action = Some(self.create_build_action(filename.clone(), move |res| {
let filename = filename.with_extension("dsb");
let mut writer = File::create(&filename).unwrap();
res.write(&mut writer).unwrap();
Action::OpenFile(filename)
}));
}
PaddedWidget::label(
self.filename
.as_ref()
.map(|f| f.display().to_string())
.unwrap_or_else(|| String::from("New File"))
).color(Color32::DARK_GRAY).ui(ui);
.unwrap_or_else(|| String::from("New File")),
)
.color(Color32::DARK_GRAY)
.ui(ui);
});
});
self.editor.show_with_completer(ui, &mut self.buffer, &self.syntax, &mut self.completer);
self.editor
.show_with_completer(ui, &mut self.buffer, &self.syntax, &mut self.completer);
if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) {
action = if let Some(filename) = self.filename.clone() {
Action::SaveFile(filename, tile_id)
Some(Action::SaveFile(filename, tile_id))
} else {
Action::RequestSaveAs(tile_id)
Some(Action::RequestSaveAs(tile_id))
}
};
Some(action)
action
}
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
}
}
+42 -13
View File
@@ -1,16 +1,18 @@
pub mod documentation;
pub mod editor;
pub mod about;
pub mod disassembler;
pub mod documentation;
pub mod editor;
pub mod io_devices;
pub mod memory;
pub mod registers;
pub mod io_devices;
use crate::state::Action;
use crate::state::Action::RequestCloseTab;
use eframe::epaint::{Color32, CornerRadius};
use egui::{Id, Response, Stroke, Ui};
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 std::any::Any;
use zxq5_egui::tabs::painted_tab;
@@ -19,7 +21,6 @@ use zxq5_egui::tabs::painted_tab;
/// type after it's been handed off to the tree (e.g. to write into the
/// editor's buffer from an `Action` handler).
pub trait TilePane: Any {
/// the title that's shown on the tab in the UI.
fn title(&self) -> egui::WidgetText;
@@ -75,15 +76,23 @@ impl Behavior<Pane> for TileBehavior {
}
fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool {
println!("on_tab_close_1");
let Some(Tile::Pane(pane)) = tiles.get_mut(tile_id) else {
return false
println!("on_tab_close_ret1");
return false;
};
println!("on_tab_close_2");
if pane.close_requires_confirmation(tile_id) {
println!("on_tab_close_ret2");
self.pending_action = Some(RequestCloseTab(tile_id));
return false;
}
println!("on_tab_close_3");
tiles.remove(tile_id);
true
}
@@ -112,7 +121,6 @@ impl Behavior<Pane> for TileBehavior {
36.0 // taller tab row = more vertical padding around the label
}
fn simplification_options(&self) -> SimplificationOptions {
SimplificationOptions {
// Keep the tab bar around even with only one tab, and don't let
@@ -122,7 +130,13 @@ impl Behavior<Pane> for TileBehavior {
}
}
fn tab_bg_color(&self, visuals: &egui::Visuals, _tiles: &Tiles<Box<dyn TilePane>>, _tile_id: TileId, tab_state: &TabState) -> egui::Color32 {
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
}
}
@@ -169,7 +183,8 @@ impl CentralTiles {
match self.tree.root {
Some(root) => {
if let Some(Tile::Container(Container::Tabs(tabs))) = self.tree.tiles.get_mut(root) {
if let Some(Tile::Container(Container::Tabs(tabs))) = self.tree.tiles.get_mut(root)
{
tabs.children.push(tile_id);
tabs.active = Some(tile_id);
} else {
@@ -205,9 +220,20 @@ impl CentralTiles {
}
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<Action> {
self.behavior.pending_action = None;
println!("centraltiles_ui_1");
// self.behavior.pending_action = None;
self.tree.ui(&mut self.behavior, ui);
self.behavior.pending_action.take()
println!("centraltiles_ui_2");
let action = self.behavior.pending_action.take();
println!("centraltiles_ui_3 {action:?}");
action
// self.behavior.pending_action.take()
}
}
@@ -219,6 +245,9 @@ impl PanelFactory {
T: TilePane + 'static,
F: Fn() -> T + 'static,
{
Self(name.to_string(), Box::new(move || Box::new(constructor()) as Box<dyn TilePane>))
Self(
name.to_string(),
Box::new(move || Box::new(constructor()) as Box<dyn TilePane>),
)
}
}
}