Skip to main content

rustapi/docks/
tilemap.rs

1use crate::editor::{RUSTERIX, SCENEMANAGER};
2use crate::prelude::*;
3use rusterix::TileRole;
4
5#[allow(dead_code)]
6#[derive(PartialEq)]
7enum AddMode {
8    Single,
9    Anim,
10    Multi,
11}
12
13use AddMode::*;
14
15pub struct TilemapDock {
16    curr_tilemap_id: Uuid,
17    add_mode: AddMode,
18
19    preview_tile: Option<rusterix::Tile>,
20}
21
22impl Dock for TilemapDock {
23    fn new() -> Self
24    where
25        Self: Sized,
26    {
27        Self {
28            curr_tilemap_id: Uuid::new_v4(),
29            add_mode: Single,
30
31            preview_tile: None,
32        }
33    }
34
35    fn setup(&mut self, _ctx: &mut TheContext) -> TheCanvas {
36        let mut canvas = TheCanvas::new();
37
38        let rgba_layout = TheRGBALayout::new(TheId::named("Tilemap Editor"));
39        canvas.set_layout(rgba_layout);
40
41        let mut toolbar_canvas = TheCanvas::new();
42        let traybar_widget = TheTraybar::new(TheId::empty());
43        toolbar_canvas.set_widget(traybar_widget);
44
45        let mut clear_button = TheTraybarButton::new(TheId::named("Tilemap Editor Clear"));
46        clear_button.set_text(fl!("clear"));
47        clear_button.set_status_text(&fl!("status_tilemap_clear_button"));
48
49        //let icon_view = TheIconView::new(TheId::named("Tilemap Editor Icon View"));
50
51        let mut toolbar_hlayout = TheHLayout::new(TheId::empty());
52        toolbar_hlayout.set_background_color(None);
53        toolbar_hlayout.set_margin(Vec4::new(10, 4, 5, 4));
54
55        /*
56        let mut tile_name_text = TheText::new(TheId::empty());
57        tile_name_text.set_text("Tags".to_string());
58        toolbar_hlayout.add_widget(Box::new(tile_name_text));
59
60        let mut tile_name_edit = TheTextLineEdit::new(TheId::named("Tilemap Editor Name Edit"));
61        tile_name_edit.limiter_mut().set_max_width(80);
62        toolbar_hlayout.add_widget(Box::new(tile_name_edit));
63
64        let mut block_name_text = TheText::new(TheId::empty());
65        block_name_text.set_text("Blocking".to_string());
66        toolbar_hlayout.add_widget(Box::new(block_name_text));
67
68        let block_check_button: TheCheckButton =
69            TheCheckButton::new(TheId::named("Tilemap Editor Block"));
70        toolbar_hlayout.add_widget(Box::new(block_check_button));
71
72        let mut hdivider = TheHDivider::new(TheId::empty());
73        hdivider.limiter_mut().set_max_width(15);
74        toolbar_hlayout.add_widget(Box::new(hdivider));
75        */
76
77        let mut drop_down = TheDropdownMenu::new(TheId::named("Tilemap Editor Role"));
78
79        for dir in TileRole::iterator() {
80            drop_down.add_option(dir.to_string().to_string());
81        }
82        toolbar_hlayout.add_widget(Box::new(drop_down));
83
84        let mut hdivider = TheHDivider::new(TheId::empty());
85        hdivider.limiter_mut().set_max_width(15);
86        toolbar_hlayout.add_widget(Box::new(hdivider));
87
88        let mut add_switch = TheGroupButton::new(TheId::named("Tilemap Editor Switch"));
89        add_switch.add_text_status("Single".to_string(), "Show tile picker.".to_string());
90        add_switch.add_text_status(
91            "Anim".to_string(),
92            "Apply procedural materials.".to_string(),
93        );
94        add_switch.add_text_status("Multi".to_string(), "Apply a color.".to_string());
95
96        add_switch.set_item_width(70);
97        add_switch.set_index(0);
98        toolbar_hlayout.add_widget(Box::new(add_switch));
99
100        let mut hdivider = TheHDivider::new(TheId::empty());
101        hdivider.limiter_mut().set_max_width(15);
102        toolbar_hlayout.add_widget(Box::new(hdivider));
103
104        let mut add_button = TheTraybarButton::new(TheId::named("Tilemap Editor Add"));
105        add_button.set_text(fl!("tilemap_add_button"));
106        add_button.set_status_text(&fl!("status_tilemap_add_button"));
107
108        toolbar_hlayout.add_widget(Box::new(add_button));
109        toolbar_hlayout.add_widget(Box::new(clear_button));
110
111        // let mut hdivider = TheHDivider::new(TheId::empty());
112        // hdivider.limiter_mut().set_max_width(15);
113        // toolbar_hlayout.add_widget(Box::new(hdivider));
114
115        let mut zoom = TheSlider::new(TheId::named("Tilemap Editor Zoom"));
116        zoom.set_value(TheValue::Float(2.0));
117        zoom.set_range(TheValue::RangeF32(0.5..=5.0));
118        zoom.set_continuous(true);
119        zoom.limiter_mut().set_max_width(120);
120        toolbar_hlayout.add_widget(Box::new(zoom));
121        toolbar_hlayout.set_reverse_index(Some(1));
122
123        // Details
124        let mut details_canvas = TheCanvas::new();
125
126        let mut vlayout = TheVLayout::new(TheId::named(" Tile Details Layout"));
127        vlayout.set_margin(Vec4::new(5, 20, 5, 10));
128        vlayout.set_alignment(TheHorizontalAlign::Center);
129        vlayout.limiter_mut().set_max_width(120);
130
131        // let mut switch_button = TheTraybarButton::new(TheId::named("Tilemap Selection Switch"));
132        // switch_button.set_text("Anim".to_string());
133        // switch_button
134        //     .set_status_text("Switches between an anim based preview and multi tiles preview.");
135
136        let mut icon_preview = TheIconView::new(TheId::named("Tilemap Selection Preview"));
137        icon_preview.set_alpha_mode(false);
138        icon_preview.limiter_mut().set_max_size(Vec2::new(100, 100));
139        icon_preview.set_border_color(Some([100, 100, 100, 255]));
140
141        // vlayout.add_widget(Box::new(switch_button));
142        // vlayout.add_widget(Box::new(icon_preview));
143
144        details_canvas.set_layout(vlayout);
145
146        toolbar_canvas.set_layout(toolbar_hlayout);
147        canvas.set_top(toolbar_canvas);
148        // canvas.set_right(details_canvas);
149
150        canvas
151    }
152
153    fn activate(
154        &mut self,
155        ui: &mut TheUI,
156        ctx: &mut TheContext,
157        project: &Project,
158        server_ctx: &mut ServerContext,
159    ) {
160        if let Some(id) = server_ctx.pc.id() {
161            if server_ctx.pc.is_tilemap() {
162                if let Some(tilemap) = project.get_tilemap(id) {
163                    self.set_tilemap(tilemap, ui, ctx);
164                }
165            }
166        }
167    }
168
169    fn supports_actions(&self) -> bool {
170        false
171    }
172
173    fn default_state(&self) -> DockDefaultState {
174        DockDefaultState::Maximized
175    }
176
177    fn handle_event(
178        &mut self,
179        event: &TheEvent,
180        ui: &mut TheUI,
181        ctx: &mut TheContext,
182        project: &mut Project,
183        _server_ctx: &mut ServerContext,
184    ) -> bool {
185        let mut redraw = false;
186
187        match event {
188            TheEvent::Custom(id, value) => {
189                if id.name == "Tilemap Grid Size Changed" {
190                    if let Some(rgba_layout) = ui.get_rgba_layout("Tilemap Editor") {
191                        if let Some(rgba_view) = rgba_layout.rgba_view_mut().as_rgba_view() {
192                            if let Some(grid_size) = value.to_i32() {
193                                rgba_view.set_grid(Some(grid_size));
194                            }
195                        }
196                    }
197                }
198            }
199            TheEvent::TileZoomBy(id, delta) => {
200                if id.name == "Tilemap Editor View" {
201                    if let Some(tilemap) = project.get_tilemap_mut(self.curr_tilemap_id) {
202                        tilemap.zoom += *delta * 0.05;
203                        tilemap.zoom = tilemap.zoom.clamp(0.5, 5.0);
204                        self.set_tilemap(tilemap, ui, ctx);
205                        ctx.ui.relayout = true;
206                    }
207                }
208            }
209            TheEvent::IndexChanged(id, index) => {
210                if id.name == "Tilemap Editor Switch" {
211                    if *index == 0 {
212                        self.add_mode = Single;
213                    } else if *index == 1 {
214                        self.add_mode = Anim;
215                    } else {
216                        self.add_mode = Multi;
217                    }
218                    self.compute_preview(project, ui);
219                    redraw = true;
220                }
221            }
222            TheEvent::ContextMenuSelected(_widget_id, item_id) => {
223                if item_id.name == "Rename Tileset" {
224                    if let Some(tilemap) = project.get_tilemap(self.curr_tilemap_id) {
225                        open_text_dialog(
226                            "Rename Tileset",
227                            "Tilset Name",
228                            tilemap.name.as_str(),
229                            self.curr_tilemap_id,
230                            ui,
231                            ctx,
232                        );
233                    }
234                } else if item_id.name == "Add Tileset Colors" {
235                    // let prev = project.palette.clone();
236                    if let Some(tilemap) = project.get_tilemap(self.curr_tilemap_id).cloned() {
237                        let width = tilemap.buffer.dim().width;
238                        let height = tilemap.buffer.dim().height;
239                        for y in 0..height {
240                            for x in 0..width {
241                                if let Some(c) = tilemap.buffer.get_pixel(x, y) {
242                                    let color = TheColor::from(c);
243                                    if color.a == 1.0 {
244                                        project.palette.add_unique_color(color);
245                                    }
246                                }
247                            }
248                        }
249                    }
250                    if let Some(palette_picker) = ui.get_palette_picker("Palette Picker") {
251                        let index = palette_picker.index();
252
253                        palette_picker.set_palette(project.palette.clone());
254                        if let Some(widget) = ui.get_widget("Palette Color Picker") {
255                            if let Some(color) = &project.palette[index] {
256                                widget.set_value(TheValue::ColorObject(color.clone()));
257                            }
258                        }
259                        if let Some(widget) = ui.get_widget("Palette Hex Edit") {
260                            if let Some(color) = &project.palette[index] {
261                                widget.set_value(TheValue::Text(color.to_hex()));
262                            }
263                        }
264                    }
265                    redraw = true;
266                }
267            }
268            TheEvent::TileSelectionChanged(id) => {
269                if id.name == "Tilemap Editor View" {
270                    self.compute_preview(project, ui);
271                }
272            }
273            TheEvent::StateChanged(id, state) => {
274                if id.name == "Tilemap Editor Clear" && *state == TheWidgetState::Clicked {
275                    self.clear(ui);
276                } else if id.name == "Tilemap Editor Add" {
277                    let clear_selection = true;
278
279                    if let Some(editor) = ui
280                        .canvas
281                        .get_layout(Some(&"Tilemap Editor".to_string()), None)
282                    {
283                        if let Some(editor) = editor.as_rgba_layout() {
284                            let mut tiles = vec![];
285
286                            if self.add_mode == Single {
287                                let sequence = editor
288                                    .rgba_view_mut()
289                                    .as_rgba_view()
290                                    .unwrap()
291                                    .selection_as_sequence();
292                                for region in sequence.regions {
293                                    let mut tile = Tile::default();
294                                    let mut s = TheRGBARegionSequence::default();
295                                    s.regions.push(region);
296                                    tile.sequence = s;
297                                    tiles.push(tile);
298                                }
299                            } else if self.add_mode == Anim {
300                                let mut tile = Tile::default();
301                                let sequence = editor
302                                    .rgba_view_mut()
303                                    .as_rgba_view()
304                                    .unwrap()
305                                    .selection_as_sequence();
306                                tile.sequence = sequence;
307                                tiles.push(tile);
308                            } else if self.add_mode == Multi {
309                                let mut tile = Tile::default();
310                                let dim = editor
311                                    .rgba_view_mut()
312                                    .as_rgba_view()
313                                    .unwrap()
314                                    .selection_as_dim();
315
316                                let mut grid_size = 16;
317
318                                if let Some(t) = project.get_tilemap(self.curr_tilemap_id) {
319                                    grid_size = t.grid_size;
320                                }
321
322                                let region = TheRGBARegion::new(
323                                    dim.x as usize * grid_size as usize,
324                                    dim.y as usize * grid_size as usize,
325                                    dim.width as usize * grid_size as usize,
326                                    dim.height as usize * grid_size as usize,
327                                );
328
329                                tile.sequence = TheRGBARegionSequence::new();
330                                tile.sequence.regions.push(region);
331                                tiles.push(tile);
332                            }
333
334                            for mut tile in tiles {
335                                // if let Some(text_line_edit) =
336                                //     ui.get_text_line_edit("Tilemap Editor Name Edit")
337                                // {
338                                //     tile.name = text_line_edit.text();
339                                // }
340
341                                // if let Some(block_widget) = ui
342                                //     .canvas
343                                //     .get_widget(Some(&"Tilemap Editor Block".to_string()), None)
344                                // {
345                                //     tile.blocking = block_widget.state() == TheWidgetState::Selected;
346                                // }
347
348                                if let Some(role_widget) =
349                                    ui.get_drop_down_menu("Tilemap Editor Role")
350                                {
351                                    let index = role_widget.selected_index();
352                                    tile.role = TileRole::from_index(index as u8);
353                                }
354
355                                // Only add if non-empty
356                                if !tile.sequence.regions.is_empty() {
357                                    /*
358                                    if let Some(layout) = ui
359                                        .canvas
360                                        .get_layout(Some(&"Tilemap Tile List".to_string()), None)
361                                    {
362                                        let list_layout_id = layout.id().clone();
363                                        if let Some(list_layout) = layout.as_list_layout() {
364                                            let mut item = TheListItem::new(TheId::named_with_id(
365                                                "Tilemap Tile",
366                                                tile.id,
367                                            ));
368                                            item.set_text(tile.name.clone());
369                                            let mut sub_text = if tile.blocking {
370                                                "Blocking".to_string()
371                                            } else {
372                                                "Non-Blocking".to_string()
373                                            };
374                                            sub_text +=
375                                                ("  ".to_string() + tile.role.to_string()).as_str();
376                                            item.set_sub_text(sub_text);
377                                            item.set_state(TheWidgetState::Selected);
378                                            item.set_size(42);
379                                            item.set_associated_layout(list_layout_id);
380                                            if let Some(t) =
381                                                project.get_tilemap(self.curr_tilemap_id)
382                                            {
383                                                item.set_icon(
384                                                    tile.sequence.regions[0]
385                                                        .scale(&t.buffer, 36, 36),
386                                                );
387                                            }
388                                            list_layout.deselect_all();
389                                            let id = item.id().clone();
390                                            list_layout.add_item(item, ctx);
391                                            ctx.ui.send_widget_state_changed(
392                                                &id,
393                                                TheWidgetState::Selected,
394                                            );
395
396                                            clear_selection = true;
397                                            redraw = true;
398                                        }
399                                    }*/
400
401                                    let id = tile.id;
402                                    // Add the local tile to the bitmmap
403                                    if let Some(tilemap) =
404                                        project.get_tilemap_mut(self.curr_tilemap_id)
405                                    {
406                                        tilemap.tiles.push(tile);
407                                        // self.set_tilemap(tilemap, ui, ctx);
408                                    }
409
410                                    if let Some(t) = project.extract_tile(&id) {
411                                        // Add it to the project
412                                        let mut texture_array: Vec<rusterix::Texture> = vec![];
413                                        for b in &t.buffer {
414                                            let mut texture = rusterix::Texture::new(
415                                                b.pixels().to_vec(),
416                                                b.dim().width as usize,
417                                                b.dim().height as usize,
418                                            );
419                                            texture.generate_normals(true);
420                                            texture_array.push(texture);
421                                        }
422                                        let mut tile = rusterix::Tile {
423                                            id: t.id,
424                                            role: rusterix::TileRole::from_index(t.role),
425                                            textures: texture_array.clone(),
426                                            module: None,
427                                            blocking: t.blocking,
428                                            scale: t.scale,
429                                            tags: t.name.clone(),
430                                        };
431                                        tile.set_default_materials();
432                                        project.tiles.insert(id, tile);
433                                    }
434
435                                    let mut rusterix = RUSTERIX.write().unwrap();
436                                    rusterix.set_tiles(project.tiles.clone(), true);
437                                    SCENEMANAGER.write().unwrap().set_tile_list(
438                                        rusterix.assets.tile_list.clone(),
439                                        rusterix.assets.tile_indices.clone(),
440                                    );
441
442                                    ctx.ui.send(TheEvent::Custom(
443                                        TheId::named("Update Tilepicker"),
444                                        TheValue::Empty,
445                                    ));
446                                    //self.update_tiles(ui, ctx, project);
447                                }
448                            }
449                        }
450                    }
451
452                    // Clear the selection if successful
453                    if clear_selection {
454                        if let Some(editor) = ui
455                            .canvas
456                            .get_layout(Some(&"Tilemap Editor".to_string()), None)
457                        {
458                            if let Some(editor) = editor.as_rgba_layout() {
459                                editor
460                                    .rgba_view_mut()
461                                    .as_rgba_view()
462                                    .unwrap()
463                                    .set_selection(FxHashSet::default());
464                            }
465                            ctx.ui.send(TheEvent::StateChanged(
466                                TheId::named("Tilemap Editor Clear"),
467                                TheWidgetState::Clicked,
468                            ))
469                        }
470                    }
471                }
472            }
473            TheEvent::KeyCodeDown(TheValue::KeyCode(key)) => {
474                if *key == TheKeyCode::Escape {
475                    self.clear(ui);
476                }
477            }
478            TheEvent::ValueChanged(_id, _value) => {}
479            _ => {}
480        }
481        redraw
482    }
483
484    /// Draw the tile preview
485    fn draw_minimap(
486        &self,
487        buffer: &mut TheRGBABuffer,
488        _project: &Project,
489        ctx: &mut TheContext,
490        server_ctx: &ServerContext,
491    ) -> bool {
492        if let Some(tile) = &self.preview_tile
493            && !tile.textures.is_empty()
494        {
495            buffer.fill(BLACK);
496            let index = server_ctx.animation_counter % tile.textures.len();
497
498            let stride: usize = buffer.stride();
499
500            let src_pixels = &tile.textures[index].data;
501            let src_w = tile.textures[index].width as f32;
502            let src_h = tile.textures[index].height as f32;
503
504            let dim = buffer.dim();
505            let dst_w = dim.width as f32;
506            let dst_h = dim.height as f32;
507
508            // Compute scale
509            let scale = (dst_w / src_w).min(dst_h / src_h);
510
511            // Scaled dimensions
512            let draw_w = src_w * scale;
513            let draw_h = src_h * scale;
514
515            // Center
516            let offset_x = ((dst_w - draw_w) * 0.5).round() as usize;
517            let offset_y = ((dst_h - draw_h) * 0.5).round() as usize;
518
519            let dst_rect = (
520                offset_x,
521                offset_y,
522                draw_w.round() as usize,
523                draw_h.round() as usize,
524            );
525
526            ctx.draw.blend_scale_chunk(
527                buffer.pixels_mut(),
528                &dst_rect,
529                stride,
530                src_pixels,
531                &(src_w as usize, src_h as usize),
532            );
533
534            return true;
535        }
536
537        false
538    }
539
540    fn supports_minimap_animation(&self) -> bool {
541        true
542    }
543}
544
545impl TilemapDock {
546    /// Set the current tilemap
547    pub fn set_tilemap(
548        &mut self,
549        tilemap: &tilemap::Tilemap,
550        ui: &mut TheUI,
551        ctx: &mut TheContext,
552    ) {
553        self.curr_tilemap_id = tilemap.id;
554
555        ui.set_widget_value("Tilemap Editor Zoom", ctx, TheValue::Float(tilemap.zoom));
556
557        if let Some(rgba_layout) = ui.get_rgba_layout("Tilemap Editor") {
558            rgba_layout.set_buffer(tilemap.buffer.clone());
559            rgba_layout.set_scroll_offset(tilemap.scroll_offset);
560            if let Some(rgba_view) = rgba_layout.rgba_view_mut().as_rgba_view() {
561                rgba_view.set_supports_external_zoom(true);
562                rgba_view.set_grid(Some(tilemap.grid_size));
563                rgba_view.set_mode(TheRGBAViewMode::TileSelection);
564                rgba_view.set_background([116, 116, 116, 255]);
565                let mut c = WHITE;
566                c[3] = 128;
567                rgba_view.set_hover_color(Some(c));
568                rgba_view.set_rectangular_selection(true);
569                rgba_view.set_dont_show_grid(true);
570                rgba_view.set_zoom(tilemap.zoom);
571
572                let mut used = FxHashSet::default();
573
574                // Compute used
575                for tile in &tilemap.tiles {
576                    for region in &tile.sequence.regions {
577                        used.insert((
578                            region.x as i32 / tilemap.grid_size,
579                            region.y as i32 / tilemap.grid_size,
580                        ));
581                    }
582                }
583                rgba_view.set_used(used);
584            }
585        }
586    }
587
588    /// Clears the selection
589    pub fn clear(&mut self, ui: &mut TheUI) {
590        if let Some(editor) = ui
591            .canvas
592            .get_layout(Some(&"Tilemap Editor".to_string()), None)
593        {
594            if let Some(editor) = editor.as_rgba_layout() {
595                editor
596                    .rgba_view_mut()
597                    .as_rgba_view()
598                    .unwrap()
599                    .set_selection(FxHashSet::default());
600            }
601        }
602        self.set_tilemap_preview(TheRGBATile::default(), ui);
603    }
604
605    /// Set the selection preview
606    pub fn set_tilemap_preview(&mut self, rgba_tile: TheRGBATile, _ui: &mut TheUI) {
607        let mut texture_array: Vec<rusterix::Texture> = vec![];
608        for b in &rgba_tile.buffer {
609            let mut texture = rusterix::Texture::new(
610                b.pixels().to_vec(),
611                b.dim().width as usize,
612                b.dim().height as usize,
613            );
614            texture.generate_normals(true);
615            texture_array.push(texture);
616        }
617        self.preview_tile = Some(rusterix::Tile {
618            id: rgba_tile.id,
619            role: rusterix::TileRole::from_index(rgba_tile.role),
620            textures: texture_array.clone(),
621            module: None,
622            blocking: rgba_tile.blocking,
623            scale: rgba_tile.scale,
624            tags: rgba_tile.name.clone(),
625        });
626
627        // if let Some(icon_view) = ui.get_icon_view("Tilemap Selection Preview") {
628        //     icon_view.set_rgba_tile(tile);
629        // }
630        // if let Some(render_view) = ui.get_render_view("MiniMap") {
631        //     let dim = *render_view.dim();
632        //     let buffer = render_view.render_buffer_mut();
633
634        //     buffer.copy_into(0, 0, &tile.buffer[0]);
635        // }
636        // *PREVIEW_ICON.write().unwrap() = (tile, 0);
637    }
638
639    /// Compute the selection preview
640    pub fn compute_preview(&mut self, project: &mut Project, ui: &mut TheUI) {
641        if let Some(rgba_layout) = ui.get_rgba_layout("Tilemap Editor") {
642            if let Some(rgba_view) = rgba_layout.rgba_view_mut().as_rgba_view() {
643                if self.add_mode == Single {
644                    let mut tile = rgba_view.selection_as_tile();
645                    if let Some(last) = tile.buffer.last() {
646                        tile.buffer = vec![last.clone()];
647                    }
648                    self.set_tilemap_preview(tile, ui);
649                } else if self.add_mode == Anim {
650                    let selection = rgba_view.selection_as_sequence();
651                    let mut tile = TheRGBATile::default();
652                    if let Some(tilemap) = project.get_tilemap(self.curr_tilemap_id) {
653                        tile.buffer = tilemap.buffer.extract_sequence(&selection);
654                    }
655                    self.set_tilemap_preview(tile, ui);
656                } else {
657                    let mut tile = TheRGBATile::default();
658                    let dim = rgba_view.selection_as_dim();
659
660                    if let Some(tilemap) = project.get_tilemap(self.curr_tilemap_id) {
661                        let region = TheRGBARegion::new(
662                            dim.x as usize * tilemap.grid_size as usize,
663                            dim.y as usize * tilemap.grid_size as usize,
664                            dim.width as usize * tilemap.grid_size as usize,
665                            dim.height as usize * tilemap.grid_size as usize,
666                        );
667                        tile.buffer.push(tilemap.buffer.extract_region(&region));
668                    }
669                    self.set_tilemap_preview(tile, ui);
670                }
671            }
672        }
673    }
674}