diff --git a/crates/opensi-editor/src/card.rs b/crates/opensi-editor/src/card.rs new file mode 100644 index 0000000..c34d3c0 --- /dev/null +++ b/crates/opensi-editor/src/card.rs @@ -0,0 +1,56 @@ +use opensi_core::prelude::*; + +#[derive(Debug, Clone, Copy)] +pub enum CardKind<'a> { + Theme(&'a Theme), + Question(&'a Question), + New, +} + +impl<'a> CardKind<'a> { + pub fn show(&self, ui: &mut egui::Ui) -> egui::Response { + let (text, fill, text_color) = match self { + CardKind::Theme(theme) => ( + theme.name.clone(), + ui.visuals().widgets.active.bg_fill, + ui.visuals().widgets.active.text_color(), + ), + CardKind::Question(question) => ( + question.price.to_string(), + egui::Color32::TRANSPARENT, + ui.visuals().widgets.inactive.text_color(), + ), + CardKind::New => ( + "➕ Новый вопрос".to_string(), + egui::Color32::TRANSPARENT, + ui.visuals().weak_text_color(), + ), + }; + let mut frame = egui::Frame::default() + .inner_margin(16.0) + .outer_margin(egui::Margin::symmetric(0.0, 4.0)) + .stroke(ui.style().visuals.widgets.noninteractive.bg_stroke) + .rounding(8.0) + .fill(fill) + .begin(ui); + + // aprox + let font_size = 22.0 - (text.len() as isize - 8).max(0) as f32 * 0.3; + frame.content_ui.add_sized( + egui::vec2(100.0, 0.0), + egui::Label::new(egui::RichText::new(&text).size(font_size).color(text_color)) + .selectable(false) + .halign(egui::Align::Center) + .wrap(), + ); + let rect = + frame.content_ui.min_rect() + frame.frame.inner_margin + frame.frame.outer_margin; + let response = ui.allocate_rect(rect, egui::Sense::click()); + if response.hovered() { + frame.frame.stroke = ui.style().visuals.widgets.active.bg_stroke; + frame.frame.fill = ui.style().visuals.widgets.active.weak_bg_fill; + } + frame.paint(ui); + response.on_hover_text(&text) + } +} diff --git a/crates/opensi-editor/src/lib.rs b/crates/opensi-editor/src/lib.rs index 48f520b..46d98e0 100644 --- a/crates/opensi-editor/src/lib.rs +++ b/crates/opensi-editor/src/lib.rs @@ -1,6 +1,7 @@ #![warn(clippy::all)] mod app; +mod card; mod file_dialogs; mod package_tab; mod package_tree; diff --git a/crates/opensi-editor/src/package_tab.rs b/crates/opensi-editor/src/package_tab.rs index e2821dd..f55df34 100644 --- a/crates/opensi-editor/src/package_tab.rs +++ b/crates/opensi-editor/src/package_tab.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use opensi_core::prelude::*; -use crate::utils::{danger_button, unselectable_heading, unselectable_label}; +use crate::utils::{danger_button, simple_row, unselectable_heading, unselectable_label}; /// Workarea tab to edit package info. pub fn package_tab(package: &mut Package, selected: &mut Option, ui: &mut egui::Ui) { @@ -43,22 +43,22 @@ fn package_info_edit(package: &mut Package, ui: &mut egui::Ui) { .cell_layout(egui::Layout::left_to_right(egui::Align::Min)) .striped(false) .body(|mut body| { - package_edit_row("Название", &mut body, |ui| { + simple_row("Название", 20.0, &mut body, |ui| { ui.text_edit_singleline(&mut package.name); }); - package_edit_row("Сложность", &mut body, |ui| { + simple_row("Сложность", 20.0, &mut body, |ui| { ui.add(egui::DragValue::new(&mut package.difficulty).range(0..=10)); }); - package_edit_row("Ограничения", &mut body, |ui| { + simple_row("Ограничения", 20.0, &mut body, |ui| { ui.text_edit_singleline(&mut package.restriction); }); - package_edit_row("Дата создания", &mut body, |ui| { + simple_row("Дата создания", 20.0, &mut body, |ui| { ui.text_edit_singleline(&mut package.date); }); - package_edit_row("Издатель", &mut body, |ui| { + simple_row("Издатель", 20.0, &mut body, |ui| { ui.text_edit_singleline(&mut package.publisher); }); - package_edit_row("Язык", &mut body, |ui| { + simple_row("Язык", 20.0, &mut body, |ui| { ui.text_edit_singleline(&mut package.language); }); }); @@ -73,28 +73,15 @@ fn package_metadata_edit(package: &Package, ui: &mut egui::Ui) { .cell_layout(egui::Layout::left_to_right(egui::Align::Min)) .striped(true) .body(|mut body| { - package_edit_row("ID пакета", &mut body, |ui| { + simple_row("ID пакета", 20.0, &mut body, |ui| { ui.label(&package.id); }); - package_edit_row("Версия пакета", &mut body, |ui| { + simple_row("Версия пакета", 20.0, &mut body, |ui| { ui.label(format!("{:.1}", package.version)); }); }); } -fn package_edit_row( - label: impl AsRef, - body: &mut egui_extras::TableBody, - content: impl FnOnce(&mut egui::Ui), -) { - body.row(20.0, |mut row| { - row.col(|ui| { - ui.label(label.as_ref()); - }); - row.col(content); - }); -} - fn package_rounds(package: &mut Package, selected: &mut Option, ui: &mut egui::Ui) { fn round_card(round: &Round, ui: &mut egui::Ui) -> egui::Response { let mut frame = egui::Frame::default() diff --git a/crates/opensi-editor/src/round_tab.rs b/crates/opensi-editor/src/round_tab.rs index 1e1998e..bb85ca8 100644 --- a/crates/opensi-editor/src/round_tab.rs +++ b/crates/opensi-editor/src/round_tab.rs @@ -1,6 +1,9 @@ use opensi_core::prelude::*; -use crate::utils::{error_label, string_list, unselectable_heading, unselectable_label}; +use crate::{ + card::CardKind, + utils::{error_label, info_edit, simple_row, unselectable_heading, unselectable_label}, +}; /// Workarea tab to edit round info and its themes. pub fn round_tab( @@ -33,7 +36,7 @@ pub fn round_tab( strip.cell(|ui| { unselectable_heading("Дополнительная информация", ui); ui.separator(); - round_info_edit(&mut round.info, ui); + info_edit(&mut round.info, ui); }); }); }); @@ -52,119 +55,22 @@ fn round_edit(round: &mut Round, ui: &mut egui::Ui) { .cell_layout(egui::Layout::left_to_right(egui::Align::Min)) .striped(false) .body(|mut body| { - round_edit_row("Название", 20.0, &mut body, |ui| { + simple_row("Название", 20.0, &mut body, |ui| { ui.text_edit_singleline(&mut round.name); }); - round_edit_row("Вариант", 20.0, &mut body, |ui| { + simple_row("Вариант", 20.0, &mut body, |ui| { // TODO: variant enum unselectable_label(format!("TODO: {:?}", round.variant), ui); }); }); } -fn round_info_edit(info: &mut Option, ui: &mut egui::Ui) { - let Some(info) = info.as_mut() else { - if ui.button("➕ Добавить информацию").clicked() { - *info = Some(Default::default()); - } - return; - }; - - egui_extras::TableBuilder::new(ui) - .id_salt("round-info-edit") - .column(egui_extras::Column::auto()) - .column(egui_extras::Column::remainder()) - .cell_layout(egui::Layout::left_to_right(egui::Align::Min)) - .striped(true) - .body(|mut body| { - round_edit_row("Авторы", 40.0, &mut body, |ui| { - string_list("round-authors", &mut info.authors, ui); - }); - round_edit_row("Источники", 40.0, &mut body, |ui| { - string_list("round-sources", &mut info.sources, ui); - }); - round_edit_row("Комментарий", 20.0, &mut body, |ui| { - ui.text_edit_singleline(&mut info.comments); - }); - round_edit_row("Расширения", 20.0, &mut body, |ui| { - ui.text_edit_singleline(&mut info.extension); - }); - }); -} - -fn round_edit_row( - label: impl AsRef, - height: f32, - body: &mut egui_extras::TableBody, - content: impl FnOnce(&mut egui::Ui), -) { - body.row(height, |mut row| { - row.col(|ui| { - ui.label(label.as_ref()); - }); - row.col(content); - }); -} - fn round_themes( package: &mut Package, idx: RoundIdx, selected: &mut Option, ui: &mut egui::Ui, ) { - #[derive(Debug, Clone, Copy)] - enum CardKind<'a> { - Theme(&'a Theme), - Question(&'a Question), - New, - } - - fn theme_table_card(kind: CardKind, ui: &mut egui::Ui) -> egui::Response { - let (text, fill, text_color) = match kind { - CardKind::Theme(theme) => ( - theme.name.clone(), - ui.visuals().widgets.active.bg_fill, - ui.visuals().widgets.active.text_color(), - ), - CardKind::Question(question) => ( - question.price.to_string(), - egui::Color32::TRANSPARENT, - ui.visuals().widgets.inactive.text_color(), - ), - CardKind::New => ( - "➕ Новый вопрос".to_string(), - egui::Color32::TRANSPARENT, - ui.visuals().weak_text_color(), - ), - }; - let mut frame = egui::Frame::default() - .inner_margin(16.0) - .outer_margin(egui::Margin::symmetric(0.0, 4.0)) - .stroke(ui.style().visuals.widgets.noninteractive.bg_stroke) - .rounding(8.0) - .fill(fill) - .begin(ui); - - // aprox - let font_size = 22.0 - (text.len() as isize - 8).max(0) as f32 * 0.3; - frame.content_ui.add_sized( - egui::vec2(100.0, 0.0), - egui::Label::new(egui::RichText::new(&text).size(font_size).color(text_color)) - .selectable(false) - .halign(egui::Align::Center) - .wrap(), - ); - let rect = - frame.content_ui.min_rect() + frame.frame.inner_margin + frame.frame.outer_margin; - let response = ui.allocate_rect(rect, egui::Sense::click()); - if response.hovered() { - frame.frame.stroke = ui.style().visuals.widgets.active.bg_stroke; - frame.frame.fill = ui.style().visuals.widgets.active.weak_bg_fill; - } - frame.paint(ui); - response.on_hover_text(&text) - } - egui_extras::StripBuilder::new(ui) .size(egui_extras::Size::remainder()) .size(egui_extras::Size::exact(30.0)) @@ -198,7 +104,7 @@ fn round_themes( }; row.col(|ui| { - if theme_table_card(CardKind::Theme(theme), ui).clicked() { + if CardKind::Theme(theme).show(ui).clicked() { *selected = Some(theme_idx.into()); } }); @@ -206,9 +112,7 @@ fn round_themes( for (question_index, question) in theme.questions.iter().enumerate() { row.col(|ui| { - if theme_table_card(CardKind::Question(question), ui) - .clicked() - { + if CardKind::Question(question).show(ui).clicked() { *selected = Some(theme_idx.question(question_index).into()); } @@ -216,7 +120,7 @@ fn round_themes( } row.col(|ui| { - if theme_table_card(CardKind::New, ui).clicked() { + if CardKind::New.show(ui).clicked() { theme.questions.push(Question { price: theme.guess_next_question_price(), ..Default::default() diff --git a/crates/opensi-editor/src/theme_tab.rs b/crates/opensi-editor/src/theme_tab.rs index e633ca8..59ab578 100644 --- a/crates/opensi-editor/src/theme_tab.rs +++ b/crates/opensi-editor/src/theme_tab.rs @@ -1,7 +1,98 @@ use opensi_core::prelude::*; -use crate::utils::todo_label; +use crate::{ + card::CardKind, + utils::{error_label, info_edit, simple_row, unselectable_heading}, +}; -pub fn theme_tab(_theme: &mut Theme, ui: &mut egui::Ui) { - todo_label(ui); +pub fn theme_tab( + package: &mut Package, + idx: ThemeIdx, + selected: &mut Option, + ui: &mut egui::Ui, +) { + ui.vertical(|ui| { + ui.allocate_ui(egui::vec2(ui.available_width(), 200.0), |ui| { + egui_extras::StripBuilder::new(ui) + .sizes(egui_extras::Size::remainder().at_most(500.0), 2) + .cell_layout(egui::Layout::top_down(egui::Align::Min)) + .horizontal(|mut strip| { + let Some(theme) = package.get_theme_mut(idx) else { + let error = format!("Невозможно найти тему с индексом {idx}"); + strip.cell(|ui| { + error_label(error, ui); + }); + strip.empty(); + return; + }; + + strip.cell(|ui| { + unselectable_heading("Тема", ui); + ui.separator(); + theme_edit(theme, ui); + }); + + strip.cell(|ui| { + unselectable_heading("Дополнительная информация", ui); + ui.separator(); + info_edit(&mut theme.info, ui); + }); + }); + }); + + unselectable_heading("Вопросы", ui); + ui.separator(); + theme_questions(package, idx, selected, ui); + }); +} + +fn theme_edit(theme: &mut Theme, ui: &mut egui::Ui) { + egui_extras::TableBuilder::new(ui) + .id_salt("theme-edit") + .column(egui_extras::Column::auto()) + .column(egui_extras::Column::remainder()) + .cell_layout(egui::Layout::left_to_right(egui::Align::Min)) + .striped(false) + .body(|mut body| { + simple_row("Название", 20.0, &mut body, |ui| { + ui.text_edit_singleline(&mut theme.name); + }); + }); +} + +fn theme_questions( + package: &mut Package, + idx: ThemeIdx, + selected: &mut Option, + ui: &mut egui::Ui, +) { + egui::ScrollArea::horizontal().show(ui, |ui| { + let Some(theme) = package.get_theme_mut(idx) else { + return; + }; + + ui.set_max_height(100.0); + + egui_extras::StripBuilder::new(ui) + .sizes(egui_extras::Size::remainder().at_least(200.0), theme.questions.len() + 1) + .cell_layout(egui::Layout::centered_and_justified(egui::Direction::LeftToRight)) + .horizontal(|mut strip| { + for (question_index, question) in theme.questions.iter().enumerate() { + strip.cell(|ui| { + if CardKind::Question(question).show(ui).clicked() { + *selected = Some(idx.question(question_index).into()); + } + }); + } + + strip.cell(|ui| { + if CardKind::New.show(ui).clicked() { + theme.questions.push(Question { + price: theme.guess_next_question_price(), + ..Default::default() + }); + } + }); + }); + }); } diff --git a/crates/opensi-editor/src/utils.rs b/crates/opensi-editor/src/utils.rs index 39ac20c..d91847b 100644 --- a/crates/opensi-editor/src/utils.rs +++ b/crates/opensi-editor/src/utils.rs @@ -108,3 +108,47 @@ pub fn string_list(id: impl Into, list: &mut Vec, ui: &mut egu }); }); } + +pub fn simple_row( + label: impl AsRef, + height: f32, + body: &mut egui_extras::TableBody, + content: impl FnOnce(&mut egui::Ui), +) { + body.row(height, |mut row| { + row.col(|ui| { + ui.label(label.as_ref()); + }); + row.col(content); + }); +} + +pub fn info_edit(info: &mut Option, ui: &mut egui::Ui) { + let Some(info) = info.as_mut() else { + if ui.button("➕ Добавить информацию").clicked() { + *info = Some(Default::default()); + } + return; + }; + + egui_extras::TableBuilder::new(ui) + .id_salt("round-info-edit") + .column(egui_extras::Column::auto()) + .column(egui_extras::Column::remainder()) + .cell_layout(egui::Layout::left_to_right(egui::Align::Min)) + .striped(true) + .body(|mut body| { + simple_row("Авторы", 40.0, &mut body, |ui| { + string_list("round-authors", &mut info.authors, ui); + }); + simple_row("Источники", 40.0, &mut body, |ui| { + string_list("round-sources", &mut info.sources, ui); + }); + simple_row("Комментарий", 20.0, &mut body, |ui| { + ui.text_edit_singleline(&mut info.comments); + }); + simple_row("Расширения", 20.0, &mut body, |ui| { + ui.text_edit_singleline(&mut info.extension); + }); + }); +} diff --git a/crates/opensi-editor/src/workarea.rs b/crates/opensi-editor/src/workarea.rs index 0721fd7..79eeb3a 100644 --- a/crates/opensi-editor/src/workarea.rs +++ b/crates/opensi-editor/src/workarea.rs @@ -24,12 +24,7 @@ fn selected_tab(package: &mut Package, selected: &mut Option, ui: & round_tab::round_tab(package, idx, selected, ui); }, &mut Some(PackageNode::Theme(idx)) => { - if let Some(theme) = package.get_theme_mut(idx) { - theme_tab::theme_tab(theme, ui); - } else { - let error = format!("Невозможно найти тему с индексом {idx}"); - error_label(error, ui); - } + theme_tab::theme_tab(package, idx, selected, ui); }, &mut Some(PackageNode::Question(idx)) => { if let Some(question) = package.get_question_mut(idx) {