Skip to content

Commit

Permalink
editor: work area tabs and package node selection
Browse files Browse the repository at this point in the history
  • Loading branch information
barsoosayque committed Sep 27, 2024
1 parent 9509401 commit 0080f5e
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 59 deletions.
59 changes: 34 additions & 25 deletions crates/opensi-editor/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use opensi_core::PackageNode;

use crate::{
file_dialogs::{self, LoadingPackageReceiver},
package_tree::{self},
workarea,
};

const FONT_REGULAR_ID: &'static str = "Regular";

Check failure on line 9 in crates/opensi-editor/src/app.rs

View workflow job for this annotation

GitHub Actions / Clippy

constants have by default a `'static` lifetime
Expand Down Expand Up @@ -63,7 +66,7 @@ impl eframe::App for EditorApp {
ui.close_menu();
}
if ui.button("💾 Сохранить").clicked() {
let PackageState::Active(ref package) = self.package_state else {
let PackageState::Active { ref package, .. } = self.package_state else {
return;
};
file_dialogs::export_dialog(package);
Expand All @@ -77,7 +80,7 @@ impl eframe::App for EditorApp {
}
}
});
if let PackageState::Active(ref _package) = self.package_state {
if let PackageState::Active { .. } = self.package_state {
ui.menu_button("Пак", |ui| {
if ui.button("❌Закрыть").clicked() {
self.package_state = PackageState::None;
Expand All @@ -88,28 +91,31 @@ impl eframe::App for EditorApp {
});
});

egui::SidePanel::left("question-tree").min_width(200.0).show(ctx, |ui| {
match self.package_state {
PackageState::Active(ref mut package) => {
package_tree::package_tree(package, ui);
},
_ => {
ui.weak("Пак не выбран");
},
}
});
if let PackageState::Active { package, selected } = &mut self.package_state {
egui::SidePanel::left("question-tree").min_width(200.0).show(ctx, |ui| {
package_tree::package_tree(package, selected, ui);
});
}

egui::CentralPanel::default().show(ctx, |ui| {
ui.with_layout(
egui::Layout::centered_and_justified(egui::Direction::LeftToRight),
|ui| {
let text = egui::RichText::new("We're so back")
.size(100.0)
.color(ui.style().visuals.weak_text_color());
ui.add(egui::Label::new(text));
},
);
});
egui::CentralPanel::default()
.frame(egui::Frame::central_panel(&ctx.style()).inner_margin(16.0))
.show(ctx, |ui| {
ui.with_layout(
egui::Layout::centered_and_justified(egui::Direction::LeftToRight),
|ui| {
if let PackageState::Active { package, selected } = &mut self.package_state
{
workarea::workarea(package, selected, ui);
} else {
let text = egui::RichText::new("OpenSI Editor")
.italics()
.size(64.0)
.color(ui.style().visuals.weak_text_color());
ui.add(egui::Label::new(text).selectable(false));
}
},
);
});
}
}

Expand All @@ -119,7 +125,10 @@ enum PackageState {
None,
#[serde(skip)]
Loading(LoadingPackageReceiver),
Active(opensi_core::Package),
Active {
package: opensi_core::Package,
selected: Option<PackageNode>,
},
}

impl PackageState {
Expand All @@ -128,7 +137,7 @@ impl PackageState {
Self::Loading(receiver) => {
match receiver.try_recv() {
Ok(Ok(package)) => {
*self = Self::Active(package);
*self = Self::Active { package, selected: None };
},
Ok(Err(_err)) => {
// TODO: error handle
Expand Down
6 changes: 6 additions & 0 deletions crates/opensi-editor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

mod app;
mod file_dialogs;
mod package_tab;
mod package_tree;
mod question_tab;
mod round_tab;
mod theme_tab;
mod utils;
mod workarea;

pub use app::EditorApp;
8 changes: 8 additions & 0 deletions crates/opensi-editor/src/package_tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use opensi_core::Package;

pub fn package_tab(package: &mut Package, ui: &mut egui::Ui) {
ui.vertical(|ui| {
ui.label(&package.id);
ui.label(package.name.clone().unwrap_or_default());
});
}
56 changes: 22 additions & 34 deletions crates/opensi-editor/src/package_tree.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
use std::borrow::Cow;

use egui::collapsing_header::CollapsingState;
use opensi_core::{Package, PackageNode};

use crate::utils::node_name;

/// Ui for a whole [`Package`] in a form of a tree.
///
/// It can add new rounds, themes and questions, edit
/// names/prices of existing ones and select them.
pub fn package_tree(package: &mut Package, ui: &mut egui::Ui) {
pub fn package_tree(package: &mut Package, selected: &mut Option<PackageNode>, ui: &mut egui::Ui) {
let name = package.name.as_ref().map(|name| name.as_str()).unwrap_or("Новый пакет вопросов");

Check failure on line 11 in crates/opensi-editor/src/package_tree.rs

View workflow job for this annotation

GitHub Actions / Clippy

called `.as_ref().map(|name| name.as_str())` on an `Option` value

ui.vertical_centered_justified(|ui| {
ui.heading(name);
let text = egui::RichText::new(name).strong().heading();
if ui.add(egui::Label::new(text).sense(egui::Sense::click()).selectable(false)).clicked() {
*selected = None;
}
});

ui.separator();

egui::ScrollArea::vertical().show(ui, |ui| {
tree_node_ui(package, None, ui);
});
egui::ScrollArea::vertical().show(ui, |ui| tree_node_ui(package, None, selected, ui));
}

/// Recursive [`PackageNode`] ui.
fn tree_node_ui<'a>(package: &mut Package, node: Option<PackageNode>, ui: &mut egui::Ui) {
fn tree_node_ui<'a>(

Check failure on line 26 in crates/opensi-editor/src/package_tree.rs

View workflow job for this annotation

GitHub Actions / Clippy

this lifetime isn't used in the function definition
package: &mut Package,
node: Option<PackageNode>,
selected: &mut Option<PackageNode>,
ui: &mut egui::Ui,
) {
fn plus_button(ui: &mut egui::Ui) -> bool {
ui.vertical_centered_justified(|ui| ui.button("➕").clicked()).inner
}
Expand Down Expand Up @@ -145,7 +151,7 @@ fn tree_node_ui<'a>(package: &mut Package, node: Option<PackageNode>, ui: &mut e
ui.weak("Нет раундов");
} else {
for index in 0..package.rounds.rounds.len() {
tree_node_ui(package, Some(PackageNode::Round { index }), ui);
tree_node_ui(package, Some(PackageNode::Round { index }), selected, ui);
}
}
});
Expand All @@ -157,11 +163,11 @@ fn tree_node_ui<'a>(package: &mut Package, node: Option<PackageNode>, ui: &mut e

let id = egui::Id::new(node.index()).with(ui.id());
match node {
node @ PackageNode::Round { index } => {
PackageNode::Round { index } => {
CollapsingState::load_with_default_open(ui.ctx(), id, true)
.show_header(ui, |ui| {
if node_button(package, node, ui) {
// TODO: selected round
*selected = Some(node);
};
})
.body(|ui| {
Expand All @@ -173,6 +179,7 @@ fn tree_node_ui<'a>(package: &mut Package, node: Option<PackageNode>, ui: &mut e
tree_node_ui(
package,
Some(PackageNode::Theme { round_index: index, index: theme_index }),
selected,
ui,
);
}
Expand All @@ -181,11 +188,11 @@ fn tree_node_ui<'a>(package: &mut Package, node: Option<PackageNode>, ui: &mut e
}
});
},
node @ PackageNode::Theme { round_index, index } => {
PackageNode::Theme { round_index, index } => {
CollapsingState::load_with_default_open(ui.ctx(), id, false)
.show_header(ui, |ui| {
if node_button(package, node, ui) {
// TODO: selected theme
*selected = Some(node);
};
})
.body(|ui| {
Expand All @@ -201,6 +208,7 @@ fn tree_node_ui<'a>(package: &mut Package, node: Option<PackageNode>, ui: &mut e
theme_index: index,
index: question_index,
}),
selected,
ui,
);
}
Expand All @@ -211,28 +219,8 @@ fn tree_node_ui<'a>(package: &mut Package, node: Option<PackageNode>, ui: &mut e
},
PackageNode::Question { round_index, theme_index, index } => {
if node_button(package, PackageNode::Question { round_index, theme_index, index }, ui) {
// TODO: selected question
*selected = Some(node);
}
},
}
}

/// Utility method to get a button name for a [`PackageNode`].
fn node_name<'a>(node: PackageNode, package: &'a Package) -> Cow<'a, str> {
match node {
PackageNode::Round { index } => package
.get_round(index)
.map(|round| round.name.as_str())
.unwrap_or("<Неизвестный раунд>")
.into(),
PackageNode::Theme { round_index, index } => package
.get_theme(round_index, index)
.map(|theme| theme.name.as_str())
.unwrap_or("<Неизвестная тема>")
.into(),
PackageNode::Question { round_index, theme_index, index } => package
.get_question(round_index, theme_index, index)
.map(|question| format!("🗛 ({})", question.price).into())
.unwrap_or("<Неизвестный вопрос>".into()),
}
}
7 changes: 7 additions & 0 deletions crates/opensi-editor/src/question_tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use opensi_core::Question;

use crate::utils::todo_label;

pub fn question_tab(_question: &mut Question, ui: &mut egui::Ui) {
todo_label(ui);
}
7 changes: 7 additions & 0 deletions crates/opensi-editor/src/round_tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use opensi_core::Round;

use crate::utils::todo_label;

pub fn round_tab(_round: &mut Round, ui: &mut egui::Ui) {
todo_label(ui);
}
7 changes: 7 additions & 0 deletions crates/opensi-editor/src/theme_tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use opensi_core::Theme;

use crate::utils::todo_label;

pub fn theme_tab(_theme: &mut Theme, ui: &mut egui::Ui) {
todo_label(ui);
}
36 changes: 36 additions & 0 deletions crates/opensi-editor/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use std::{borrow::Cow, fmt::Display};

use opensi_core::{Package, PackageNode};

/// A generic error label.
pub fn error_label(error: impl Display, ui: &mut egui::Ui) {
let text = egui::RichText::new(error.to_string()).color(egui::Color32::RED).size(24.0);
ui.add(egui::Label::new(text).selectable(true).wrap());
}

/// A stub todo label.
pub fn todo_label(ui: &mut egui::Ui) {
let text =
egui::RichText::new("TODO").background_color(egui::Color32::YELLOW).strong().size(24.0);
ui.add(egui::Label::new(text).selectable(false).extend());
}

/// Utility method to get a button name for a [`PackageNode`].
pub fn node_name<'a>(node: PackageNode, package: &'a Package) -> Cow<'a, str> {
match node {
PackageNode::Round { index } => package
.get_round(index)
.map(|round| round.name.as_str())
.unwrap_or("<Неизвестный раунд>")
.into(),
PackageNode::Theme { round_index, index } => package
.get_theme(round_index, index)
.map(|theme| theme.name.as_str())
.unwrap_or("<Неизвестная тема>")
.into(),
PackageNode::Question { round_index, theme_index, index } => package
.get_question(round_index, theme_index, index)
.map(|question| format!("🗛 ({})", question.price).into())
.unwrap_or("<Неизвестный вопрос>".into()),
}
}
Loading

0 comments on commit 0080f5e

Please sign in to comment.