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}