veloren_voxygen/ui/
mod.rs

1mod cache;
2mod event;
3pub mod graphic;
4mod scale;
5mod widgets;
6#[macro_use]
7pub mod img_ids;
8#[macro_use]
9pub mod fonts;
10#[cfg(feature = "egui-ui")] pub mod egui;
11pub mod ice;
12pub mod keyed_jobs;
13
14pub use event::Event;
15pub use graphic::{Graphic, Id as GraphicId, Rotation, SampleStrat, Transform};
16pub use keyed_jobs::KeyedJobs;
17pub use scale::{Scale, ScaleMode};
18pub use widgets::{
19    image_frame::ImageFrame,
20    image_slider::ImageSlider,
21    ingame::{Ingame, Ingameable},
22    item_tooltip::{ItemTooltip, ItemTooltipManager, ItemTooltipable},
23    outlined_text::OutlinedText,
24    radio_list::RadioList,
25    slot,
26    toggle_button::ToggleButton,
27    tooltip::{Tooltip, TooltipManager, Tooltipable},
28};
29
30use crate::{
31    error::Error,
32    render::{
33        DynamicModel, Mesh, RenderError, Renderer, UiBoundLocals, UiDrawer, UiLocals, UiMode,
34        UiVertex, create_ui_quad, create_ui_tri,
35    },
36    window::Window,
37};
38#[rustfmt::skip]
39use ::image::GenericImageView;
40use cache::Cache;
41use common::{slowjob::SlowJobPool, util::srgba_to_linear};
42use common_base::span;
43use conrod_core::{
44    Rect, Scalar, UiBuilder, UiCell,
45    event::Input,
46    graph::{self, Graph},
47    image::{Id as ImageId, Map},
48    input::{Motion, Widget, touch::Touch},
49    render::{Primitive, PrimitiveKind},
50    text::{self, font},
51    widget::{self, id::Generator},
52};
53use core::{convert::TryInto, f64, ops::Range};
54use graphic::TexId;
55use hashbrown::hash_map::Entry;
56use std::time::Duration;
57use tracing::{error, warn};
58use vek::*;
59
60#[derive(Debug)]
61pub enum UiError {
62    RenderError(RenderError),
63}
64
65enum DrawKind {
66    Image(TexId),
67    // Text and non-textured geometry
68    Plain,
69}
70
71enum DrawCommand {
72    Draw { kind: DrawKind, verts: Range<u32> },
73    Scissor(Aabr<u16>),
74    WorldPos(Option<usize>),
75}
76impl DrawCommand {
77    fn image(verts: Range<usize>, id: TexId) -> DrawCommand {
78        DrawCommand::Draw {
79            kind: DrawKind::Image(id),
80            verts: verts
81                .start
82                .try_into()
83                .expect("Vertex count for UI rendering does not fit in a u32!")
84                ..verts
85                    .end
86                    .try_into()
87                    .expect("Vertex count for UI rendering does not fit in a u32!"),
88        }
89    }
90
91    fn plain(verts: Range<usize>) -> DrawCommand {
92        DrawCommand::Draw {
93            kind: DrawKind::Plain,
94            verts: verts
95                .start
96                .try_into()
97                .expect("Vertex count for UI rendering does not fit in a u32!")
98                ..verts
99                    .end
100                    .try_into()
101                    .expect("Vertex count for UI rendering does not fit in a u32!"),
102        }
103    }
104}
105
106pub struct Ui {
107    pub ui: conrod_core::Ui,
108    image_map: Map<(graphic::Id, Rotation)>,
109    cache: Cache,
110    // Draw commands for the next render
111    draw_commands: Vec<DrawCommand>,
112    // Mesh buffer for UI vertices; we reuse its allocation in order to limit vector reallocations
113    // during redrawing.
114    mesh: Mesh<UiVertex>,
115    // Model for drawing the ui
116    model: DynamicModel<UiVertex>,
117    // Consts for default ui drawing position (ie the interface)
118    interface_locals: UiBoundLocals,
119    // Consts to specify positions of ingame elements (e.g. Nametags)
120    ingame_locals: Vec<UiBoundLocals>,
121    // Whether the window was resized since the last maintain, for updating scaling
122    window_resized: bool,
123    // Scale factor changed
124    scale_factor_changed: Option<f64>,
125    // Used to delay cache resizing until after current frame is drawn
126    need_cache_resize: bool,
127    // Whether a graphic was replaced with replaced_graphic since the last maintain call
128    graphic_replaced: bool,
129    // Scaling of the ui
130    scale: Scale,
131    // Tooltips
132    tooltip_manager: TooltipManager,
133    // Item tooltips manager
134    item_tooltip_manager: ItemTooltipManager,
135    // Scissor for the whole window
136    window_scissor: Aabr<u16>,
137}
138
139impl Ui {
140    pub fn new(window: &mut Window) -> Result<Self, Error> {
141        let scale_factor = window.scale_factor();
142        let renderer = window.renderer_mut();
143        let physical_resolution = renderer.resolution();
144        let scale = Scale::new(
145            physical_resolution,
146            scale_factor,
147            ScaleMode::Absolute(1.0),
148            1.0,
149        );
150
151        let win_dims = scale.scaled_resolution().into_array();
152
153        let mut ui = UiBuilder::new(win_dims).build();
154        // NOTE: Since we redraw the actual frame each time whether or not the UI needs
155        // to be updated, there's no reason to set the redraw count higher than
156        // 1.
157        ui.set_num_redraw_frames(1);
158
159        let item_tooltip_manager = ItemTooltipManager::new(
160            Duration::from_millis(1),
161            Duration::from_millis(0),
162            scale.scale_factor_logical(),
163        );
164
165        let tooltip_manager = TooltipManager::new(
166            ui.widget_id_generator(),
167            Duration::from_millis(1),
168            Duration::from_millis(0),
169            scale.scale_factor_logical(),
170        );
171
172        let interface_locals = renderer.create_ui_bound_locals(&[UiLocals::default()]);
173
174        Ok(Self {
175            ui,
176            image_map: Map::new(),
177            cache: Cache::new(renderer)?,
178            draw_commands: Vec::new(),
179            mesh: Mesh::new(),
180            model: renderer.create_dynamic_model(100),
181            interface_locals,
182            ingame_locals: Vec::new(),
183            window_resized: false,
184            scale_factor_changed: None,
185            need_cache_resize: false,
186            graphic_replaced: false,
187            scale,
188            tooltip_manager,
189            item_tooltip_manager,
190            window_scissor: default_scissor(physical_resolution),
191        })
192    }
193
194    // Set the scaling mode of the ui.
195    pub fn set_scaling_mode(&mut self, mode: ScaleMode) {
196        self.scale.set_scaling_mode(mode);
197        // To clear the cache (it won't be resized in this case)
198        self.need_cache_resize = true;
199        // Give conrod the new size.
200        let (w, h) = self.scale.scaled_resolution().into_tuple();
201        self.ui.handle_event(Input::Resize(w, h));
202    }
203
204    pub fn scale_factor_changed(&mut self, scale_factor: f64) {
205        self.scale_factor_changed = Some(scale_factor);
206    }
207
208    // Get a copy of Scale
209    pub fn scale(&self) -> Scale { self.scale }
210
211    pub fn add_graphic(&mut self, graphic: Graphic) -> ImageId {
212        self.image_map
213            .insert((self.cache.add_graphic(graphic), Rotation::None))
214    }
215
216    pub fn add_graphic_with_rotations(&mut self, graphic: Graphic) -> img_ids::Rotations {
217        let graphic_id = self.cache.add_graphic(graphic);
218        img_ids::Rotations {
219            none: self.image_map.insert((graphic_id, Rotation::None)),
220            cw90: self.image_map.insert((graphic_id, Rotation::Cw90)),
221            cw180: self.image_map.insert((graphic_id, Rotation::Cw180)),
222            cw270: self.image_map.insert((graphic_id, Rotation::Cw270)),
223            // Hacky way to make sure a source rectangle always faces north regardless of player
224            // orientation.
225            // This is an easy way to get around Conrod's lack of rotation data for images (for this
226            // specific use case).
227            source_north: self.image_map.insert((graphic_id, Rotation::SourceNorth)),
228            // Hacky way to make sure a target rectangle always faces north regardless of player
229            // orientation.
230            // This is an easy way to get around Conrod's lack of rotation data for images (for this
231            // specific use case).
232            target_north: self.image_map.insert((graphic_id, Rotation::TargetNorth)),
233        }
234    }
235
236    pub fn replace_graphic(&mut self, id: ImageId, graphic: Graphic) {
237        if let Some(&(graphic_id, _)) = self.image_map.get(&id) {
238            self.cache.replace_graphic(graphic_id, graphic);
239            self.image_map.replace(id, (graphic_id, Rotation::None));
240            self.graphic_replaced = true;
241        } else {
242            error!("Failed to replace graphic, the provided id is not in use.");
243        };
244    }
245
246    pub fn new_font(&mut self, font: ice::RawFont) -> font::Id {
247        let font = text::Font::from_bytes(font.0).unwrap();
248        self.ui.fonts.insert(font)
249    }
250
251    pub fn id_generator(&mut self) -> Generator { self.ui.widget_id_generator() }
252
253    pub fn set_widgets(&mut self) -> (UiCell, &mut ItemTooltipManager, &mut TooltipManager) {
254        (
255            self.ui.set_widgets(),
256            &mut self.item_tooltip_manager,
257            &mut self.tooltip_manager,
258        )
259    }
260
261    pub fn set_item_widgets(&mut self) -> (UiCell, &mut ItemTooltipManager) {
262        (self.ui.set_widgets(), &mut self.item_tooltip_manager)
263    }
264
265    // Accepts Option so widget can be unfocused.
266    pub fn focus_widget(&mut self, id: Option<widget::Id>) {
267        self.ui.keyboard_capture(match id {
268            Some(id) => id,
269            None => self.ui.window,
270        });
271    }
272
273    // Get id of current widget capturing keyboard.
274    pub fn widget_capturing_keyboard(&self) -> Option<widget::Id> {
275        self.ui.global_input().current.widget_capturing_keyboard
276    }
277
278    // Get whether a widget besides the window is capturing the mouse.
279    pub fn no_widget_capturing_mouse(&self) -> bool {
280        self.ui
281            .global_input()
282            .current
283            .widget_capturing_mouse
284            .filter(|id| id != &self.ui.window)
285            .is_none()
286    }
287
288    // Get the widget graph.
289    pub fn widget_graph(&self) -> &Graph { self.ui.widget_graph() }
290
291    pub fn handle_event(&mut self, event: Event) {
292        match event.0 {
293            Input::Resize(w, h) => {
294                if w > 0.0 && h > 0.0 {
295                    self.window_resized = true;
296                }
297            },
298            Input::Touch(touch) => self.ui.handle_event(Input::Touch(Touch {
299                xy: self.scale.scale_point(touch.xy.into()).into_array(),
300                ..touch
301            })),
302            Input::Motion(motion) => self.ui.handle_event(Input::Motion(match motion {
303                Motion::MouseCursor { x, y } => {
304                    let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
305                    Motion::MouseCursor { x, y }
306                },
307                Motion::MouseRelative { x, y } => {
308                    let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
309                    Motion::MouseRelative { x, y }
310                },
311                Motion::Scroll { x, y } => {
312                    let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple();
313                    Motion::Scroll { x, y }
314                },
315                _ => motion,
316            })),
317            _ => self.ui.handle_event(event.0),
318        }
319    }
320
321    pub fn widget_input(&self, id: widget::Id) -> Widget { self.ui.widget_input(id) }
322
323    pub fn maintain(
324        &mut self,
325        renderer: &mut Renderer,
326        pool: Option<&SlowJobPool>,
327        view_projection_mat: Option<Mat4<f32>>,
328    ) {
329        span!(_guard, "maintain", "Ui::maintain");
330        // Maintain tooltip manager
331        self.tooltip_manager
332            .maintain(self.ui.global_input(), self.scale.scale_factor_logical());
333
334        // Maintain tooltip manager
335        self.item_tooltip_manager
336            .maintain(self.ui.global_input(), self.scale.scale_factor_logical());
337
338        // Handle scale factor changing
339        let need_resize = if let Some(scale_factor) = self.scale_factor_changed.take() {
340            self.scale.scale_factor_changed(scale_factor)
341        } else {
342            false
343        };
344
345        // Used to tell if we need to clear out the draw commands (which contain scissor
346        // commands that can be invalidated by this change)
347        let physical_resolution_changed = renderer.resolution() != self.scale.physical_resolution();
348
349        // Handle window resizing.
350        let need_resize = if self.window_resized {
351            self.window_resized = false;
352            let surface_resolution = renderer.resolution();
353            let (old_w, old_h) = self.scale.scaled_resolution().into_tuple();
354            self.scale.surface_resized(surface_resolution);
355            let (w, h) = self.scale.scaled_resolution().into_tuple();
356            self.ui.handle_event(Input::Resize(w, h));
357            self.window_scissor = default_scissor(surface_resolution);
358
359            // Avoid panic in graphic cache when minimizing.
360            // Avoid resetting cache if window size didn't change
361            // Somewhat inefficient for elements that won't change size after a window
362            // resize
363            let res = renderer.resolution();
364            res.x > 0 && res.y > 0 && !(old_w == w && old_h == h)
365        } else {
366            false
367        } || need_resize;
368
369        if need_resize {
370            self.need_cache_resize = true;
371        }
372
373        if self.need_cache_resize {
374            // Resize graphic cache
375            // FIXME: Handle errors here.
376            self.cache.resize(renderer).unwrap();
377
378            self.need_cache_resize = false;
379        }
380
381        let mut retry = false;
382        self.maintain_internal(
383            renderer,
384            pool,
385            view_projection_mat,
386            &mut retry,
387            physical_resolution_changed,
388        );
389        if retry {
390            // Update the glyph cache and try again.
391            self.maintain_internal(renderer, pool, view_projection_mat, &mut retry, false);
392        }
393    }
394
395    fn maintain_internal(
396        &mut self,
397        renderer: &mut Renderer,
398        pool: Option<&SlowJobPool>,
399        view_projection_mat: Option<Mat4<f32>>,
400        retry: &mut bool,
401        physical_resolution_changed: bool,
402    ) {
403        span!(_guard, "internal", "Ui::maintain_internal");
404        let (graphic_cache, text_cache, glyph_cache, cache_tex) = self.cache.cache_mut_and_tex();
405
406        // If the physical resolution changed draw commands need to be cleared since
407        // scissors commands will be invalid. A resize usually means everything
408        // needs to be redrawn anyway but certain error cases below can cause an
409        // early return.
410        if physical_resolution_changed {
411            self.draw_commands.clear();
412        }
413
414        let mut primitives = if *retry || self.graphic_replaced || physical_resolution_changed {
415            // If this is a retry, always redraw.
416            //
417            // Also redraw if a texture was swapped out by replace_graphic in order to
418            // regenerate invalidated textures and clear out any invalid `TexId`s.
419            //
420            // Also redraw if the physical resolution changed since we need to regenerate
421            // the invalid scissor rect commands.
422            self.graphic_replaced = false;
423            self.ui.draw()
424        } else {
425            // Otherwise, redraw only if widgets were actually updated.
426            match self.ui.draw_if_changed() {
427                Some(primitives) => primitives,
428                None => return,
429            }
430        };
431
432        let (half_res, x_align, y_align) = {
433            let res = renderer.resolution();
434            (
435                res.map(|e| e as f32 / 2.0),
436                (res.x & 1) as f32 * 0.5,
437                (res.y & 1) as f32 * 0.5,
438            )
439        };
440
441        let ui = &self.ui;
442        let p_scale_factor = self.scale.scale_factor_physical();
443        // Functions for converting for conrod scalar coords to GL vertex coords (-1.0
444        // to 1.0).
445        let (ui_win_w, ui_win_h) = (ui.win_w, ui.win_h);
446        let vx = |x: f64| (x / ui_win_w * 2.0) as f32;
447        let vy = |y: f64| (y / ui_win_h * 2.0) as f32;
448        let gl_aabr = |rect: Rect| {
449            let (l, r, b, t) = rect.l_r_b_t();
450            let min = Vec2::new(
451                ((vx(l) * half_res.x + x_align).round() - x_align) / half_res.x,
452                ((vy(b) * half_res.y + y_align).round() - y_align) / half_res.y,
453            );
454            let max = Vec2::new(
455                ((vx(r) * half_res.x + x_align).round() - x_align) / half_res.x,
456                ((vy(t) * half_res.y + y_align).round() - y_align) / half_res.y,
457            );
458            Aabr { min, max }
459        };
460
461        // let window_dim = ui.window_dim();
462        let theme = &ui.theme;
463        let widget_graph = ui.widget_graph();
464        let fonts = &ui.fonts;
465        let dpi_factor = p_scale_factor as f32;
466
467        // We can use information about whether a widget was actually updated to more
468        // easily track cache invalidations.
469        let updated_widgets = ui.updated_widgets();
470        let mut glyph_missing = false;
471
472        updated_widgets.iter()
473            // Filter out widgets that are either:
474            // - not text primitives, or
475            // - are text primitives, but were both already in the cache, and not updated this
476            //  frame.
477            //
478            // The reason the second condition is so complicated is that we want to handle cases
479            // where we cleared the whole cache, which can result in glyphs from text updated in a
480            // previous frame not being present in the text cache.
481            .filter_map(|(&widget_id, updated)| {
482                widget_graph.widget(widget_id)
483                    .and_then(|widget| Some((widget.rect, widget.unique_widget_state::<widget::Text>()?)))
484                    .and_then(|(rect, text)| {
485                        // NOTE: This fallback is weird and probably shouldn't exist.
486                        let font_id = text.style.font_id(theme)/*.or_else(|| fonts.ids().next())*/?;
487                        let font = fonts.get(font_id)?;
488
489                        Some((widget_id, updated, rect, text, font_id, font))
490                    })
491            })
492            // Recache the entry.
493            .for_each(|(widget_id, updated, rect, graph::UniqueWidgetState { state, style }, font_id, font)| {
494                let entry = match text_cache.entry(widget_id) {
495                    Entry::Occupied(_) if !updated => return,
496                    entry => entry,
497                };
498
499                // Retrieve styling.
500                let color = style.color(theme);
501                let font_size = style.font_size(theme);
502                let line_spacing = style.line_spacing(theme);
503                let justify = style.justify(theme);
504                let y_align = conrod_core::position::Align::End;
505
506                let text = &state.string;
507                let line_infos = &state.line_infos;
508
509                // Convert conrod coordinates to pixel coordinates.
510                let trans_x = |x: Scalar| (x + ui_win_w / 2.0) * dpi_factor as Scalar;
511                let trans_y = |y: Scalar| ((-y) + ui_win_h / 2.0) * dpi_factor as Scalar;
512
513                // Produce the text layout iterators.
514                let lines = line_infos.iter().map(|info| &text[info.byte_range()]);
515                let line_rects = text::line::rects(line_infos.iter(), font_size, rect,
516                                                   justify, y_align, line_spacing);
517
518                // Grab the positioned glyphs from the text primitives.
519                let scale = text::f32_pt_to_scale(font_size as f32 * dpi_factor);
520                let positioned_glyphs = lines.zip(line_rects).flat_map(|(line, line_rect)| {
521                    let (x, y) = (trans_x(line_rect.left()) as f32, trans_y(line_rect.bottom()) as f32);
522                    let point = text::rt::Point { x, y };
523                    font.layout(line, scale, point)
524                });
525
526                // Reuse the mesh allocation if possible at this entry if possible; we
527                // then clear it to avoid using stale entries.
528                let mesh = entry.or_insert_with(Mesh::new);
529                mesh.clear();
530
531                let color = srgba_to_linear(color.to_fsa().into());
532
533                positioned_glyphs.for_each(|g| {
534                    match glyph_cache.rect_for(font_id.index(), &g) {
535                        Ok(Some((uv_rect, screen_rect))) => {
536                            let uv = Aabr {
537                                min: Vec2::new(uv_rect.min.x, uv_rect.max.y),
538                                max: Vec2::new(uv_rect.max.x, uv_rect.min.y),
539                            };
540                            let rect = Aabr {
541                                min: Vec2::new(
542                                    vx(screen_rect.min.x as f64 / p_scale_factor
543                                        - ui.win_w / 2.0),
544                                    vy(ui.win_h / 2.0
545                                        - screen_rect.max.y as f64 / p_scale_factor),
546                                ),
547                                max: Vec2::new(
548                                    vx(screen_rect.max.x as f64 / p_scale_factor
549                                        - ui.win_w / 2.0),
550                                    vy(ui.win_h / 2.0
551                                        - screen_rect.min.y as f64 / p_scale_factor),
552                                ),
553                            };
554                            mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text));
555                        },
556                        // Glyph not found, no-op.
557                        Ok(None) => {},
558                        // Glyph was found, but was not yet cached; we'll need to add it to the
559                        // cache and retry.
560                        Err(_) => {
561                            // Queue the unknown glyph to be cached.
562                            glyph_missing = true;
563                        }
564                    }
565
566                    // NOTE: Important to do this for *all* glyphs to try to make sure that
567                    // any that are uncached are part of the graph.  Because we always
568                    // clear the entire cache whenever a new glyph is encountered, by
569                    // adding and checking all glyphs as they come in we guarantee that (1)
570                    // any glyphs in the text cache are in the queue, and (2) any glyphs
571                    // not in the text cache are either in the glyph cache, or (during our
572                    // processing here) we set the retry flag to force a glyph cache
573                    // update.  Setting the flag causes all glyphs in the current queue to
574                    // become part of the glyph cache during the second call to
575                    // `maintain_internal`, so as long as the cache refresh succeeded,
576                    // during the second call all glyphs will hit this branch as desired.
577                    glyph_cache.queue_glyph(font_id.index(), g);
578                });
579            });
580
581        if glyph_missing {
582            if *retry {
583                // If a glyph was missing and this was our second try, we know something was
584                // messed up during the glyph_cache redraw.  It is possible that
585                // the queue contained unneeded glyphs, so we don't necessarily
586                // have to give up; a more precise enumeration of the
587                // glyphs required to render this frame might work out.  However, this is a
588                // pretty remote edge case, so we opt to not care about this
589                // frame (we skip rendering it, basically), and just clear the
590                // text cache and glyph queue; next frame will then
591                // start out with an empty slate, and therefore will enqueue precisely the
592                // glyphs needed for that frame.  If *that* frame fails, we're
593                // in bigger trouble.
594                text_cache.clear();
595                glyph_cache.clear();
596                glyph_cache.clear_queue();
597                self.ui.needs_redraw();
598                warn!("Could not recache queued glyphs, skipping frame.");
599            } else {
600                // NOTE: If this is the first round after encountering a new glyph, we just
601                // refresh the whole glyph cache.  Technically this is not necessary since
602                // positioned_glyphs should still be accurate, but it's just the easiest way
603                // to ensure that all glyph positions get updated.  It also helps keep the glyph
604                // cache reasonable by making sure any glyphs that subsequently get rendered are
605                // actually in the cache, including glyphs that were mapped to ids but didn't
606                // happen to be rendered on the frame where the cache was
607                // refreshed.
608                text_cache.clear();
609                tracing::debug!("Updating glyphs and clearing text cache.");
610
611                if let Err(err) = glyph_cache.cache_queued(|rect, data| {
612                    let offset = [rect.min.x, rect.min.y];
613                    let size = [rect.width(), rect.height()];
614
615                    let new_data = data
616                        .iter()
617                        .map(|x| [255, 255, 255, *x])
618                        .collect::<Vec<[u8; 4]>>();
619
620                    renderer.update_texture(&cache_tex.0, offset, size, &new_data);
621                }) {
622                    // FIXME: If we actually hit this error, it's still possible we could salvage
623                    // things in various ways (for instance, the current queue might have extra
624                    // stuff in it, so we could try calling maintain_internal a
625                    // third time with a fully clean queue; or we could try to
626                    // increase the glyph texture size, etc.  But hopefully
627                    // we will not actually encounter this error.
628                    warn!("Failed to cache queued glyphs: {:?}", err);
629
630                    // Clear queued glyphs, so that (hopefully) next time we won't have the
631                    // offending glyph or glyph set.  We then exit the loop and don't try to
632                    // rerender the frame.
633                    glyph_cache.clear_queue();
634                    self.ui.needs_redraw();
635                } else {
636                    // Successfully cached, so repeat the loop.
637                    *retry = true;
638                }
639            }
640
641            return;
642        }
643
644        self.draw_commands.clear();
645        let mesh = &mut self.mesh;
646        mesh.clear();
647
648        enum State {
649            Image(TexId),
650            Plain,
651        }
652
653        let mut current_state = State::Plain;
654        let mut start = 0;
655
656        let window_scissor = self.window_scissor;
657        let mut current_scissor = window_scissor;
658
659        let mut ingame_local_index = 0;
660
661        enum Placement {
662            Interface,
663            // Number of primitives left to render ingame and visibility
664            InWorld(usize, bool),
665        }
666
667        let mut placement = Placement::Interface;
668
669        // Switches to the `Plain` state and completes the previous `Command` if not
670        // already in the `Plain` state.
671        macro_rules! switch_to_plain_state {
672            () => {
673                if let State::Image(id) = current_state {
674                    self.draw_commands
675                        .push(DrawCommand::image(start..mesh.vertices().len(), id));
676                    start = mesh.vertices().len();
677                    current_state = State::Plain;
678                }
679            };
680        }
681
682        while let Some(prim) = primitives.next() {
683            let Primitive {
684                kind,
685                scizzor,
686                rect,
687                id: widget_id,
688            } = prim;
689            // Check for a change in the scissor.
690            let new_scissor = {
691                let (l, b, w, h) = scizzor.l_b_w_h();
692                let scale_factor = self.scale.scale_factor_physical();
693                // Calculate minimum x and y coordinates while
694                // flipping y axis (from +up to +down) and
695                // moving origin to top-left corner (from middle).
696                let min_x = ui.win_w / 2.0 + l;
697                let min_y = ui.win_h / 2.0 - b - h;
698                let intersection = Aabr {
699                    min: Vec2 {
700                        x: (min_x * scale_factor) as u16,
701                        y: (min_y * scale_factor) as u16,
702                    },
703                    max: Vec2 {
704                        x: ((min_x + w) * scale_factor) as u16,
705                        y: ((min_y + h) * scale_factor) as u16,
706                    },
707                }
708                .intersection(window_scissor);
709
710                if intersection.is_valid() && intersection.size().map(|s| s > 0).reduce_and() {
711                    intersection
712                } else {
713                    // TODO: What should we return here
714                    // We used to return a zero sized aabr but it's invalid to
715                    // use a zero sized scissor so for now we just don't change
716                    // the scissor.
717                    current_scissor
718                }
719            };
720            if new_scissor != current_scissor {
721                // Finish the current command.
722                self.draw_commands.push(match current_state {
723                    State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
724                    State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id),
725                });
726                start = mesh.vertices().len();
727
728                // Update the scissor and produce a command.
729                current_scissor = new_scissor;
730                self.draw_commands.push(DrawCommand::Scissor(new_scissor));
731            }
732
733            match placement {
734                // No primitives left to place in the world at the current position, go back to
735                // drawing the interface
736                Placement::InWorld(0, _) => {
737                    placement = Placement::Interface;
738                    // Finish current state
739                    self.draw_commands.push(match current_state {
740                        State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
741                        State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id),
742                    });
743                    start = mesh.vertices().len();
744                    // Push new position command
745                    self.draw_commands.push(DrawCommand::WorldPos(None));
746                },
747                // Primitives still left to draw ingame
748                Placement::InWorld(num_prims, visible) => match kind {
749                    // Other types aren't drawn & shouldn't decrement the number of primitives left
750                    // to draw ingame
751                    PrimitiveKind::Other(_) => {},
752                    // Decrement the number of primitives left
753                    _ => {
754                        placement = Placement::InWorld(num_prims - 1, visible);
755                        // Behind the camera
756                        if !visible {
757                            continue;
758                        }
759                    },
760                },
761                Placement::Interface => {},
762            }
763
764            match kind {
765                PrimitiveKind::Image {
766                    image_id,
767                    color,
768                    source_rect,
769                } => {
770                    let (graphic_id, rotation) = self
771                        .image_map
772                        .get(&image_id)
773                        .expect("Image does not exist in image map");
774                    let gl_aabr = gl_aabr(rect);
775                    let (source_aabr, gl_size) = {
776                        // Transform the source rectangle into uv coordinate.
777                        // TODO: Make sure this is right.  Especially the conversions.
778                        let ((uv_l, uv_r, uv_b, uv_t), gl_size) =
779                            match graphic_cache.get_graphic(*graphic_id) {
780                                Some(Graphic::Blank) | None => continue,
781                                Some(Graphic::Image(image, ..)) => {
782                                    source_rect.and_then(|src_rect| {
783                                        let (image_w, image_h) = image.dimensions();
784                                        let (source_w, source_h) = src_rect.w_h();
785                                        let gl_size = gl_aabr.size();
786                                        if image_w == 0
787                                            || image_h == 0
788                                            || source_w < 1.0
789                                            || source_h < 1.0
790                                            || gl_size.reduce_partial_min() < f32::EPSILON
791                                        {
792                                            None
793                                        } else {
794                                            // Multiply drawn image size by ratio of original image
795                                            // size to
796                                            // source rectangle size (since as the proportion of the
797                                            // image gets
798                                            // smaller, the drawn size should get bigger), up to the
799                                            // actual
800                                            // size of the original image.
801                                            let ratio_x = (image_w as f64 / source_w).min(
802                                                (image_w as f64 / (gl_size.w * half_res.x) as f64)
803                                                    .max(1.0),
804                                            );
805                                            let ratio_y = (image_h as f64 / source_h).min(
806                                                (image_h as f64 / (gl_size.h * half_res.y) as f64)
807                                                    .max(1.0),
808                                            );
809                                            let (l, r, b, t) = src_rect.l_r_b_t();
810                                            Some((
811                                                (
812                                                    l / image_w as f64, /* * ratio_x */
813                                                    r / image_w as f64, /* * ratio_x */
814                                                    b / image_h as f64, /* * ratio_y */
815                                                    t / image_h as f64, /* * ratio_y */
816                                                ),
817                                                Extent2::new(
818                                                    (gl_size.w as f64 * ratio_x) as f32,
819                                                    (gl_size.h as f64 * ratio_y) as f32,
820                                                ),
821                                            ))
822                                            /* ((l / image_w as f64),
823                                            (r / image_w as f64),
824                                            (b / image_h as f64),
825                                            (t / image_h as f64)) */
826                                        }
827                                    })
828                                },
829                                // No easy way to interpret source_rect for voxels...
830                                Some(Graphic::Voxel(..)) => None,
831                            }
832                            .unwrap_or_else(|| ((0.0, 1.0, 0.0, 1.0), gl_aabr.size()));
833                        (
834                            Aabr {
835                                min: Vec2::new(uv_l, uv_b),
836                                max: Vec2::new(uv_r, uv_t),
837                            },
838                            gl_size,
839                        )
840                    };
841
842                    let resolution = Vec2::new(
843                        (gl_size.w * half_res.x).round() as u16,
844                        (gl_size.h * half_res.y).round() as u16,
845                    );
846
847                    // Don't do anything if resolution is zero
848                    if resolution.map(|e| e == 0).reduce_or() {
849                        continue;
850                        // TODO: consider logging unneeded elements
851                    }
852
853                    let color =
854                        srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
855
856                    // Cache graphic at particular resolution.
857                    let (uv_aabr, scale, tex_id) = match graphic_cache.cache_res(
858                        renderer,
859                        pool,
860                        *graphic_id,
861                        resolution,
862                        source_aabr,
863                        *rotation,
864                    ) {
865                        // TODO: get dims from graphic_cache (or have it return floats directly)
866                        Some(((aabr, scale), tex_id)) => {
867                            let cache_dims = graphic_cache
868                                .get_tex(tex_id)
869                                .0
870                                .get_dimensions()
871                                .xy()
872                                .map(|e| e as f32);
873                            let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims;
874                            let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims;
875                            (Aabr { min, max }, scale, tex_id)
876                        },
877                        None => continue,
878                    };
879
880                    match current_state {
881                        // Switch to the image state if we are not in it already.
882                        State::Plain => {
883                            self.draw_commands
884                                .push(DrawCommand::plain(start..mesh.vertices().len()));
885                            start = mesh.vertices().len();
886                            current_state = State::Image(tex_id);
887                        },
888                        // If the image is cached in a different texture switch to the new one
889                        State::Image(id) if id != tex_id => {
890                            self.draw_commands
891                                .push(DrawCommand::image(start..mesh.vertices().len(), id));
892                            start = mesh.vertices().len();
893                            current_state = State::Image(tex_id);
894                        },
895                        State::Image(_) => {},
896                    }
897
898                    mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, match *rotation {
899                        Rotation::None | Rotation::Cw90 | Rotation::Cw180 | Rotation::Cw270 => {
900                            UiMode::Image { scale }
901                        },
902                        Rotation::SourceNorth => UiMode::ImageSourceNorth { scale },
903                        Rotation::TargetNorth => UiMode::ImageTargetNorth { scale },
904                    }));
905                },
906                PrimitiveKind::Text { .. } => {
907                    switch_to_plain_state!();
908
909                    // Mesh should already be cached.
910                    mesh.push_mesh(text_cache.get(&widget_id).unwrap_or(&Mesh::new()));
911                },
912                PrimitiveKind::Rectangle { color } => {
913                    let color = srgba_to_linear(color.to_fsa().into());
914                    // Don't draw a transparent rectangle.
915                    if color[3] == 0.0 {
916                        continue;
917                    }
918
919                    switch_to_plain_state!();
920
921                    mesh.push_quad(create_ui_quad(
922                        gl_aabr(rect),
923                        Aabr {
924                            min: Vec2::zero(),
925                            max: Vec2::zero(),
926                        },
927                        color,
928                        UiMode::Geometry,
929                    ));
930                },
931                PrimitiveKind::TrianglesSingleColor { color, triangles } => {
932                    // Don't draw transparent triangle or switch state if there are actually no
933                    // triangles.
934                    let color = srgba_to_linear(Rgba::from(Into::<[f32; 4]>::into(color)));
935                    if triangles.is_empty() || color[3] == 0.0 {
936                        continue;
937                    }
938
939                    switch_to_plain_state!();
940
941                    for tri in triangles {
942                        let p1 = Vec2::new(vx(tri[0][0]), vy(tri[0][1]));
943                        let p2 = Vec2::new(vx(tri[1][0]), vy(tri[1][1]));
944                        let p3 = Vec2::new(vx(tri[2][0]), vy(tri[2][1]));
945                        // If triangle is clockwise, reverse it.
946                        let (v1, v2): (Vec3<f32>, Vec3<f32>) = ((p2 - p1).into(), (p3 - p1).into());
947                        let triangle = if v1.cross(v2).z > 0.0 {
948                            [p1.into_array(), p2.into_array(), p3.into_array()]
949                        } else {
950                            [p2.into_array(), p1.into_array(), p3.into_array()]
951                        };
952                        mesh.push_tri(create_ui_tri(
953                            triangle,
954                            [[0.0; 2]; 3],
955                            color,
956                            UiMode::Geometry,
957                        ));
958                    }
959                },
960                PrimitiveKind::Other(container) => {
961                    if container.type_id == std::any::TypeId::of::<widgets::ingame::State>() {
962                        // Calculate the scale factor to pixels at this 3d point using the camera.
963                        if let Some(view_projection_mat) = view_projection_mat {
964                            // Retrieve world position
965                            let parameters = container
966                                .state_and_style::<widgets::ingame::State, widgets::ingame::Style>()
967                                .unwrap()
968                                .state
969                                .parameters;
970
971                            let pos_on_screen = (view_projection_mat
972                                * Vec4::from_point(parameters.pos))
973                            .homogenized();
974                            let visible = if pos_on_screen.z > 0.0 && pos_on_screen.z < 1.0 {
975                                let x = pos_on_screen.x;
976                                let y = pos_on_screen.y;
977                                let (w, h) = parameters.dims.into_tuple();
978                                let (half_w, half_h) = (w / ui_win_w as f32, h / ui_win_h as f32);
979                                (x - half_w < 1.0 && x + half_w > -1.0)
980                                    && (y - half_h < 1.0 && y + half_h > -1.0)
981                            } else {
982                                false
983                            };
984                            // Don't process ingame elements outside the frustum
985                            placement = if visible {
986                                // Finish current state
987                                self.draw_commands.push(match current_state {
988                                    State::Plain => {
989                                        DrawCommand::plain(start..mesh.vertices().len())
990                                    },
991                                    State::Image(id) => {
992                                        DrawCommand::image(start..mesh.vertices().len(), id)
993                                    },
994                                });
995                                start = mesh.vertices().len();
996
997                                // Push new position command
998                                let world_pos = Vec4::from_point(parameters.pos);
999                                if self.ingame_locals.len() > ingame_local_index {
1000                                    renderer.update_consts(
1001                                        &mut self.ingame_locals[ingame_local_index],
1002                                        &[world_pos.into()],
1003                                    )
1004                                } else {
1005                                    self.ingame_locals
1006                                        .push(renderer.create_ui_bound_locals(&[world_pos.into()]));
1007                                }
1008                                self.draw_commands
1009                                    .push(DrawCommand::WorldPos(Some(ingame_local_index)));
1010                                ingame_local_index += 1;
1011
1012                                Placement::InWorld(parameters.num, true)
1013                            } else {
1014                                Placement::InWorld(parameters.num, false)
1015                            };
1016                        }
1017                    }
1018                },
1019                _ => {}, /* TODO: Add this.
1020                          *PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind
1021                          * multicolor with id {:?}", id);} */
1022            }
1023        }
1024        // Enter the final command.
1025        self.draw_commands.push(match current_state {
1026            State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
1027            State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id),
1028        });
1029
1030        /* // Draw glyph cache (use for debugging).
1031        self.draw_commands
1032            .push(DrawCommand::Scissor(default_scissor(renderer)));
1033        start = mesh.vertices().len();
1034        mesh.push_quad(create_ui_quad(
1035            Aabr {
1036                min: (-1.0, -1.0).into(),
1037                max: (1.0, 1.0).into(),
1038            },
1039            Aabr {
1040                min: (0.0, 1.0).into(),
1041                max: (1.0, 0.0).into(),
1042            },
1043            Rgba::new(1.0, 1.0, 1.0, 0.8),
1044            UiMode::Text,
1045        ));
1046        self.draw_commands
1047            .push(DrawCommand::plain(start..mesh.vertices().len())); */
1048
1049        // Create a larger dynamic model if the mesh is larger than the current model
1050        // size.
1051        if self.model.len() < self.mesh.vertices().len() {
1052            self.model = renderer.create_dynamic_model(self.mesh.vertices().len() * 4 / 3);
1053        }
1054        // Update model with new mesh.
1055        renderer.update_model(&self.model, &self.mesh, 0);
1056    }
1057
1058    pub fn render<'pass, 'data: 'pass>(&'data self, drawer: &mut UiDrawer<'_, 'pass>) {
1059        span!(_guard, "render", "Ui::render");
1060        let mut drawer = drawer.prepare(&self.interface_locals, &self.model, self.window_scissor);
1061        for draw_command in self.draw_commands.iter() {
1062            match draw_command {
1063                DrawCommand::Scissor(new_scissor) => {
1064                    drawer.set_scissor(*new_scissor);
1065                },
1066                DrawCommand::WorldPos(index) => {
1067                    drawer.set_locals(
1068                        index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]),
1069                    );
1070                },
1071                DrawCommand::Draw { kind, verts } => {
1072                    let tex = match kind {
1073                        DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id),
1074                        DrawKind::Plain => self.cache.glyph_cache_tex(),
1075                    };
1076                    drawer.draw(tex.1, verts.clone()); // Note: trivial clone
1077                },
1078            }
1079        }
1080    }
1081}
1082
1083fn default_scissor(physical_resolution: Vec2<u32>) -> Aabr<u16> {
1084    let (screen_w, screen_h) = physical_resolution.into_tuple();
1085    Aabr {
1086        min: Vec2 { x: 0, y: 0 },
1087        max: Vec2 {
1088            x: screen_w as u16,
1089            y: screen_h as u16,
1090        },
1091    }
1092}