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