diff options
| author | Rubén Beltrán del Río <jj@r.bdr.sh> | 2025-12-26 21:17:05 +0100 |
|---|---|---|
| committer | Rubén Beltrán del Río <jj@r.bdr.sh> | 2025-12-26 21:25:41 +0100 |
| commit | 5cb79e3f86b5328301eabbbc409f254b58ee9bf4 (patch) | |
| tree | 78d48265bc840b4e7564ed38b3ba9a3026001fdb /src | |
Initial build
Diffstat (limited to 'src')
| -rw-r--r-- | src/constants.rs | 3 | ||||
| -rw-r--r-- | src/main.rs | 291 | ||||
| -rw-r--r-- | src/stages.rs | 60 |
3 files changed, 354 insertions, 0 deletions
diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..2292b66 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,3 @@ +pub const MIN_ZOOM: f64 = 0.1; +pub const MAX_ZOOM: f64 = 2.0; +pub const ZOOM_STEP: f64 = 0.1; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e99316c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,291 @@ +mod constants; +mod stages; + +mod icon_names { + include!(concat!(env!("OUT_DIR"), "/icon_names.rs")); +} + +use wmap_parser::{parse, Map}; +use wmap_renderer::{render_to_surface, StageType, Configuration}; +use gtk::prelude::*; +use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent}; +use sourceview5::prelude::{BufferExt, TextBufferExt, TextViewExt}; +use sourceview5::LanguageManager; +use stages::all_stages; +use crate::icon_names::shipped::{ZOOM_IN_REGULAR, ZOOM_OUT_REGULAR, IMAGE_REGULAR, PLUS, SPLIT_HORIZONTAL_REGULAR, SPLIT_VERTICAL_REGULAR}; +use cairo::ImageSurface; + +struct AppModel { + orientation: gtk::Orientation, + stage_type_list: gtk::StringList, + stage_type: StageType, + source: sourceview5::Buffer, + map: Map, + zoom: f64, + render_configuration: Configuration, + surface: ImageSurface, + surface_width: i32, + surface_height: i32, + drawing_area: gtk::DrawingArea, +} + +#[derive(Debug)] +enum AppAction { + ChangeOrientation, + StageTypeSelected(usize), + SourceChanged, + Zoom(f64), + ZoomOut, + ZoomIn, +} + +impl AppModel { + fn update_image(&mut self) { + if let Ok(surface) = render_to_surface(&self.map, self.stage_type, &self.render_configuration) { + self.surface_width = (surface.width() as f64 * self.zoom).round() as i32; + self.surface_height = (surface.height() as f64 * self.zoom).round() as i32; + self.surface = surface; + + let zoom = self.zoom; + + let surface_clone = self.surface.clone(); + self.drawing_area.set_draw_func(move |_, cr, _width, _height| { + cr.scale(zoom, zoom); + cr.set_source_surface(&surface_clone, 0.0, 0.0).ok(); + cr.paint().ok(); + }); + + self.drawing_area.set_content_width(self.surface_width); + self.drawing_area.set_content_height(self.surface_height); + self.drawing_area.queue_draw(); + } + } +} + +#[relm4::component] +impl SimpleComponent for AppModel { + /// The type of the messages that this component can receive. + type Input = AppAction; + /// The type of the messages that this component can send. + type Output = (); + /// The type of data with which this component will be initialized. + type Init = f64; + + view! { + gtk::Window { + set_title: Some("Map"), + set_default_width: 300, + set_default_height: 100, + + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 0, + + gtk::HeaderBar { + pack_start = >k::Box { + set_orientation: gtk::Orientation::Horizontal, + + gtk::Button::with_label("New") { + set_icon_name: PLUS + }, + gtk::Button::with_label("Change Orientation") { + #[watch] + set_icon_name: match model.orientation { + gtk::Orientation::Horizontal => SPLIT_VERTICAL_REGULAR, + _ => SPLIT_HORIZONTAL_REGULAR + }, + connect_clicked => AppAction::ChangeOrientation + } + }, + pack_end = >k::Box { + set_orientation: gtk::Orientation::Horizontal, + + gtk::Button::with_label("Print") { + set_icon_name: IMAGE_REGULAR + }, + + gtk::DropDown { + set_model: Some(&model.stage_type_list), + set_show_arrow: true, + set_selected: 0, + connect_selected_notify[sender] => move |dropdown| { + let selected = dropdown.selected(); + sender.input(AppAction::StageTypeSelected(selected as usize)); + }, + } + + } + }, + + gtk::Paned { + #[watch] + set_orientation: model.orientation, + set_expand: true, + + #[wrap(Some)] + set_start_child = >k::ScrolledWindow { + sourceview5::View { + set_expand: true, + set_monospace: true, + set_buffer: Some(&model.source) + } + }, + + #[wrap(Some)] + set_end_child = >k::ScrolledWindow { + #[local_ref] + drawing_area -> gtk::DrawingArea { + #[watch] + set_content_width: model.surface_width, + #[watch] + set_content_height: model.surface_height + } + } + + // #[wrap(Some)] + // set_end_child = >k::DrawingArea { + // set_expand: true + // } + }, + + gtk::ActionBar { + pack_end = >k::Box { + set_orientation: gtk::Orientation::Horizontal, + + gtk::Label { + #[watch] + set_label: &format!("{:.1}x", model.zoom), + set_margin_end: 5 + }, + + gtk::Button::with_label("Zoom Out") { + set_icon_name: ZOOM_OUT_REGULAR, + set_margin_end: 5, + add_css_class: "flat", + add_css_class: "zoom", + add_css_class: "zoom-out", + connect_clicked => AppAction::ZoomOut + }, + + gtk::Scale { + set_orientation: gtk::Orientation::Horizontal, + #[watch] + set_value: model.zoom, + set_draw_value: false, + set_range: (constants::MIN_ZOOM, constants::MAX_ZOOM), + set_increments: (constants::ZOOM_STEP, constants::ZOOM_STEP), + set_value_pos: gtk::PositionType::Left, + set_width_request: 100, + connect_value_changed[sender] => move |scale| { + sender.input(AppAction::Zoom(scale.value())); + } + }, + + gtk::Button::with_label("Zoom In") { + set_icon_name: ZOOM_IN_REGULAR, + add_css_class: "flat", + add_css_class: "zoom", + add_css_class: "zoom-in", + connect_clicked => AppAction::ZoomIn + }, + } + } + } + } + } + + /// Initialize the UI and model. + fn init( + zoom: Self::Init, + root: Self::Root, + sender: ComponentSender<Self>, + ) -> ComponentParts<Self> { + let stage_types: Vec<&str> = all_stages() + .iter() + .map(|stage_type| stage_type.name()) + .collect(); + let stage_type_list = gtk::StringList::new(&stage_types); + + let source = sourceview5::Buffer::new(None); + let cloned_sender = sender.clone(); + source.connect_changed(move |_| { + cloned_sender.input(AppAction::SourceChanged); + }); + + // Enable syntax highlighting + source.set_highlight_syntax(true); + + // Set default language + let language_manager = LanguageManager::default(); + if let Some(language) = language_manager.language("wmap") { + source.set_language(Some(&language)); + } + + let drawing_area = gtk::DrawingArea::new(); + + let model = AppModel { + orientation: gtk::Orientation::Horizontal, + stage_type_list, + stage_type: StageType::Practice, + source, + map: parse(""), + zoom, + render_configuration: Configuration::default(), + surface: ImageSurface::create(cairo::Format::ARgb32, 0, 0).unwrap(), + surface_width: 0, + surface_height: 0, + drawing_area: drawing_area.clone(), + }; + + let widgets = view_output!(); + + ComponentParts { model, widgets } + } + + fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) { + match message { + AppAction::ChangeOrientation => { + self.orientation = match self.orientation { + gtk::Orientation::Horizontal => gtk::Orientation::Vertical, + _ => gtk::Orientation::Horizontal, + } + } + AppAction::StageTypeSelected(index) => { + let all_stages = all_stages(); + if index < all_stages.len() { + self.stage_type = all_stages[index]; + } + self.update_image(); + } + AppAction::SourceChanged => { + let text = + self.source + .text(&self.source.start_iter(), &self.source.end_iter(), false); + self.map = parse(&text); + self.update_image(); + } + AppAction::Zoom(new_zoom) => { + self.zoom = new_zoom; + self.update_image(); + }, + AppAction::ZoomOut => { + if self.zoom - constants::ZOOM_STEP >= constants::MIN_ZOOM { + self.zoom -= constants::ZOOM_STEP; + self.update_image(); + } + } + AppAction::ZoomIn => { + if self.zoom < constants::MAX_ZOOM { + self.zoom += constants::ZOOM_STEP; + self.update_image(); + } + } + } + } +} + +fn main() { + relm4_icons::initialize_icons(icon_names::GRESOURCE_BYTES, icon_names::RESOURCE_PREFIX); + let app = RelmApp::new("relm4.test.simple_manual"); + app.run::<AppModel>(1.0); +} diff --git a/src/stages.rs b/src/stages.rs new file mode 100644 index 0000000..28079bd --- /dev/null +++ b/src/stages.rs @@ -0,0 +1,60 @@ +use wmap_renderer::{StageType}; + +pub fn all_stages() -> &'static [StageType] { + &[ + StageType::Activities, + StageType::Practice, + StageType::Data, + StageType::Knowledge, + StageType::Ubiquity, + StageType::Certainty, + StageType::PublicationTypes, + StageType::Market, + StageType::KnowledgeManagement, + StageType::MarketPerception, + StageType::UserPerception, + StageType::PerceptionInIndustry, + StageType::FocusOfValue, + StageType::Understanding, + StageType::Comparison, + StageType::Failure, + StageType::MarketAction, + StageType::Efficiency, + StageType::DecisionDrivers, + StageType::Behavior, + ] +} + +pub fn types() -> &'static [StageType] { + &[ + StageType::Activities, + StageType::Practice, + StageType::Data, + StageType::Knowledge, + ] +} + +pub fn characteristics() -> &'static [StageType] { + &[ + StageType::Ubiquity, + StageType::Certainty, + StageType::PublicationTypes, + ] +} + +pub fn properties() -> &'static [StageType] { + &[ + StageType::Market, + StageType::KnowledgeManagement, + StageType::MarketPerception, + StageType::UserPerception, + StageType::PerceptionInIndustry, + StageType::FocusOfValue, + StageType::Understanding, + StageType::Comparison, + StageType::Failure, + StageType::MarketAction, + StageType::Efficiency, + StageType::DecisionDrivers, + ] +} |