aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorRubén Beltrán del Río <jj@r.bdr.sh>2025-12-26 21:17:05 +0100
committerRubén Beltrán del Río <jj@r.bdr.sh>2025-12-26 21:25:41 +0100
commit5cb79e3f86b5328301eabbbc409f254b58ee9bf4 (patch)
tree78d48265bc840b4e7564ed38b3ba9a3026001fdb /src/main.rs
Initial build
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs291
1 files changed, 291 insertions, 0 deletions
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 = &gtk::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 = &gtk::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 = &gtk::ScrolledWindow {
+ sourceview5::View {
+ set_expand: true,
+ set_monospace: true,
+ set_buffer: Some(&model.source)
+ }
+ },
+
+ #[wrap(Some)]
+ set_end_child = &gtk::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 = &gtk::DrawingArea {
+ // set_expand: true
+ // }
+ },
+
+ gtk::ActionBar {
+ pack_end = &gtk::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);
+}