gooey-rs 0.12.1

Tile-based UI library with audio support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
use std::iter::FromIterator;
use vec_map::VecMap;

use nsys::{self, geometry, math};
use nsys::color::{self, Color};
use nsys::gl::{self, glium, image};

use crate::Tree;
use crate::interface::{self, view, View};

pub (crate) fn update <V : AsRef <View>> (
  render         : &mut gl::Render,
  tileset_id     : Option <gl::render::resource::DefaultTilesetId>,
  view_tree      : &Tree <V>,
  draw_crosshair : bool
) {
  use color::Normalize;
  use gl::render::resource::{draw2d, MAIN_VIEWPORT};
  use view::component::{Canvas, Kind};
  log::trace!("update...");
  let tileset_id           = tileset_id.unwrap_or_default();
  let mut rectangles       = vec![];
  let mut tiles            = vec![];
  let mut rectangles_64x64 = vec![];
  let mut draw_indices     = vec![];
  // screen is the root canvas element
  let screen_id        = view_tree.root_node_id().unwrap();
  let mut view_ids     = view_tree.traverse_pre_order_ids (screen_id).unwrap();
  let mut style        = view::Style::default();
  let mut draw_pointer = None;
  view_ids.next().map (|screen_id|{
    let screen_view = view_tree.get (&screen_id).unwrap().data().as_ref();
    set_screen_view (render, screen_view);
    // TODO: currently this uses the default style colors if no style is present;
    // possibly use inverse color tiles
    screen_view.appearance.style.as_ref().map (|s| style = s.clone());
    draw_pointer = screen_view.appearance.pointer.clone();
    let (fg, bg) = {
      let fg = style.fg.raw().normalize().into();
      let bg = style.bg.raw().normalize().into();
      (fg, bg)
    };
    let canvas = Canvas::try_ref (&screen_view.component).unwrap();
    // TODO: should screen allow a background rectangle ?
    let border_tiles = canvas_border (canvas);
    if !border_tiles.is_empty() {
      // add color
      let border_tiles = border_tiles.into_iter().map (
        |gl::vertex::Vert2dTile { tile, row, column }|
        gl::vertex::Vert2dTileColor { tile, row, column, fg, bg }
      ).collect::<Vec<_>>();
      let tiles_len = tiles.len() as u32;
      draw_indices.push (draw2d::DrawIndices {
        rectangle:        None,
        draw_tiles:       None,
        draw_color_tiles: Some (
          draw2d::Tiles {
            vertex_range: tiles_len..tiles_len + border_tiles.len() as u32,
            origin: (0, 0).into(),
            tileset_id
          }
        )
      });
      tiles.extend (border_tiles);
    }
  }).unwrap();
  for id in view_ids {
    let view = view_tree.get (&id).unwrap().data().as_ref();
    if view.appearance.pointer.is_some() {
      draw_pointer = view.appearance.pointer.clone();
    }
    match &view.component {
      view::Component::Canvas (canvas) => {
        let style = view.appearance.style.as_ref().unwrap_or (&style);
        let rectangle =
          if let Some (vertex) = canvas_background (canvas, style) {
            rectangles.push (vertex);
            Some (rectangles.len() as u32 - 1)
          } else {
            None
          };
        let border_tiles = canvas_border (canvas);
        let (body_tiles, body_rectangles_64x64) = {
          let mut tiles            = vec![];
          let mut rectangles_64x64 = vec![];
          for child in view_tree.children (&id).unwrap()
            .map (|node| node.data().as_ref())
          {
            match &child.component {
              view::Component::Body (body) => {
                let style = child.appearance.style.as_ref().unwrap_or (style);
                tiles.extend (canvas_body (canvas, body, style))
              }
              view::Component::Image (image) =>
                rectangles_64x64.push (canvas_rectangle_uv (canvas, image)),
              _ => {}
            }
          }
          (tiles, rectangles_64x64)
        };
        let draw_color_tiles = if !border_tiles.is_empty() || !body_tiles.is_empty() {
          // add color
          // TODO: currently this uses the default style colors if no style is present;
          // instead use inherited parent style
          // TODO: body tiles use child view style
          let (fg, bg) = {
            let style = view.appearance.style.clone().unwrap_or_default();
            ( style.fg.raw().normalize().into(),
              style.bg.raw().normalize().into() )
          };
          let border_tiles = border_tiles.into_iter().map (
            |gl::vertex::Vert2dTile { tile, row, column }|
            gl::vertex::Vert2dTileColor { tile, row, column, fg, bg }
          ).collect::<Vec<_>>();
          let tiles_len    = tiles.len() as u32;
          let vertex_range = tiles_len..tiles_len
            + border_tiles.len() as u32 + body_tiles.len() as u32;
          tiles.extend (border_tiles);
          tiles.extend (body_tiles);
          Some (draw2d::Tiles {
            vertex_range, tileset_id, origin: (0, 0).into()
          })
        } else {
          None
        };
        if rectangle.is_some() || draw_color_tiles.is_some() {
          draw_indices.push (draw2d::DrawIndices {
            rectangle,
            draw_color_tiles,
            draw_tiles: None
          });
        }
        rectangles_64x64.extend (body_rectangles_64x64);
      }
      // TODO: more components
      _ => {}
    }
  }
  if draw_pointer.is_some() {
    let (x, y) = {
      let pointer = interface::POINTER.read().unwrap();
      (pointer.position_horizontal, pointer.position_vertical)
    };
    render.resource.set_pointer_position (&render.glium_display, [x, y].into())
  }
  render.resource.draw2d.draw_pointer = draw_pointer.map (|pointer| pointer.0);
  // upload the vertex buffer data
  render.resource.draw2d.rectangle_2d_vertices  =
    glium::VertexBuffer::dynamic (&render.glium_display, &rectangles).unwrap();
  render.resource.draw2d.tile_color_2d_vertices =
    glium::VertexBuffer::dynamic (&render.glium_display, &tiles).unwrap();
  render.resource.draw2d.rectangle_64x64_vertices  =
    glium::VertexBuffer::dynamic (&render.glium_display, &rectangles_64x64).unwrap();
  // set the viewport resources
  // TODO: this will need to be revisited to support multiple viewports
  render.resource.draw2d.viewport_resources_set (MAIN_VIEWPORT,
    draw2d::ViewportResources {
      draw_crosshair, draw_indices, .. draw2d::ViewportResources::default()
    }
  );
  log::trace!("...update");
}

/// Updates the back-end viewport size, clear color, and border. View parameter must be
/// a Canvas component.
pub (crate) fn set_screen_view (render : &mut gl::Render, view : &View) {
  use color::Normalize;
  use view::component::{canvas, Canvas, Kind};
  let canvas = Canvas::try_ref (&view.component).unwrap();
  // viewport size
  // NOTE: we restrict the viewport to be minimum size of 1x1
  match canvas.coordinates.dimensions() {
    view::coordinates::Dimensions::Pixel (pixel) => {
      // TODO: only call render update methods if screen view has changed
      let width  = pixel.width();
      let height = pixel.height();
      render.window_resized ([
        u32::max (width,  1),
        u32::max (height, 1)
      ].into());
      render.camera2d_move_origin_to_bottom_left();
    }
    view::coordinates::Dimensions::Tile (_) => unreachable!()
  }
  // canvas clear color
  let mut set_clear_color = |color : Color|
    render.clear_color = color.raw().normalize().into();
  match canvas.clear_color {
    canvas::ClearColor::Appearance =>
      if let Some (style) = view.appearance.style.as_ref() {
        set_clear_color (style.bg);
      } else {
        log::debug!("TODO: no appearance clear color");
      }
    canvas::ClearColor::Fixed (Some (color)) => set_clear_color (color),
    canvas::ClearColor::Fixed (None) => log::warn!("TODO: no fixed clear color")
  }
  // canvas border
  if let Some (_border) = canvas.border.as_ref() {
    log::warn!("TODO: screen border");
  }
}

pub (crate) fn load_pointer (
  render : &mut gl::Render,
  key    : gl::render::resource::PointerTextureIndexRepr,
  bytes  : &[u8],
  offset : math::Vector2 <i16>
) {
  let texture = gl::texture::texture2d_with_mipmaps_from_bytes (
    &render.glium_display,
    bytes,
    image::ImageFormat::Png,
    glium::texture::MipmapsOption::NoMipmap
  ).unwrap();
  render.resource.textures_pointer.insert (key as usize, (texture, offset));
}

/// NOTE: loads anysize textures starting from index 1 (index 0 is reserved for the
/// pointer texture)
pub (crate) fn load_textures (
  render           : &mut gl::Render,
  textures_16x16   : &[&'static str],
  textures_64x64   : &[&'static str],
  textures_anysize : &[&'static str]
) {
  // TODO: clear current textures if no paths are given ?
  if !textures_16x16.is_empty() {
    let textures = gl::texture::texture2darray_with_mipmaps_from_files (
      &render.glium_display,
      textures_16x16,
      image::ImageFormat::Png,
      glium::texture::MipmapsOption::NoMipmap
    ).unwrap();
    render.resource.set_textures_16x16 (textures);
  }
  if !textures_64x64.is_empty() {
    let textures = gl::texture::texture2darray_with_mipmaps_from_files (
      &render.glium_display,
      textures_64x64,
      image::ImageFormat::Png,
      glium::texture::MipmapsOption::NoMipmap
    ).unwrap();
    render.resource.set_textures_64x64 (textures);
  }
  render.resource.textures_anysize = VecMap::from_iter (textures_anysize.iter()
    .map (|path| {
      gl::texture::texture2d_with_mipmaps_from_file (
        &render.glium_display,
        path,
        image::ImageFormat::Png,
        glium::texture::MipmapsOption::NoMipmap
      ).unwrap()
    }).enumerate().map (|(i, texture)| (i+1, texture))
  );
}

/// Convert an interface canvas+appearance into a rectangle vertex for the renderer.
///
/// If the canvas clear color is set to `ClearColor::Fixed(None)`, then no vertex will
/// be returned.
fn canvas_background (
  canvas : &view::component::Canvas,
  style  : &view::Style
) -> Option <gl::vertex::Vert2dRectColor> {
  use color::Normalize;
  use view::coordinates;
  use view::component::canvas;
  let color = {
    let color = match canvas.clear_color {
      canvas::ClearColor::Appearance => style.bg,
      canvas::ClearColor::Fixed (Some (color)) => color,
      canvas::ClearColor::Fixed (None) => return None
    };
    color.raw().normalize().into()
  };
  let (bottom_left, dimensions) = {
    let body_coordinates = canvas.body_coordinates();
    if body_coordinates.dimensions().horizontal() == 0
      || body_coordinates.dimensions().vertical() == 0
    {
      return None
    }
    let aabb       = geometry::integer::Aabb2::from (body_coordinates);
    let aabb_pixel = match canvas.coordinates.kind() {
      coordinates::Kind::Tile  => coordinates::tile_to_pixel_aabb (aabb),
      coordinates::Kind::Pixel => aabb
    };
    ( aabb_pixel.min().numcast().unwrap().0.into_array(),
      [*aabb_pixel.width() as f32, *aabb_pixel.height() as f32]
    )
  };
  Some (gl::vertex::Vert2dRectColor { bottom_left, dimensions, color })
}

/// Generate canvas border tiles
fn canvas_border (canvas : &view::component::Canvas) -> Vec <gl::vertex::Vert2dTile> {
  use gl::vertex::Vert2dTile;
  use view::coordinates;
  let mut tiles = vec![];
  if let Some (border) = canvas.border.as_ref() {
    assert!(canvas.coordinates.kind() == coordinates::Kind::Tile,
      "TODO: support pixel borders");
    debug_assert!(border.top          <= u8::MAX as u32);
    debug_assert!(border.left         <= u8::MAX as u32);
    debug_assert!(border.right        <= u8::MAX as u32);
    debug_assert!(border.bottom       <= u8::MAX as u32);
    debug_assert!(border.top_left     <= u8::MAX as u32);
    debug_assert!(border.top_right    <= u8::MAX as u32);
    debug_assert!(border.bottom_left  <= u8::MAX as u32);
    debug_assert!(border.bottom_right <= u8::MAX as u32);
    let [row, column]   = [
      canvas.coordinates.position_vertical(),
      canvas.coordinates.position_horizontal()
    ];
    let [rows, columns] = [
      canvas.coordinates.dimensions_vertical()   as i32,
      canvas.coordinates.dimensions_horizontal() as i32
    ];
    if rows > 0 && columns > 0 {
      let (thickness_top, thickness_bottom, thickness_left, thickness_right) = (
        border.thickness_top    as i32,
        border.thickness_bottom as i32,
        border.thickness_left   as i32,
        border.thickness_right  as i32
      );
      let mut push_tile = |row, column, tile|
        tiles.push (Vert2dTile { row, column, tile: tile as u8 });
      // top
      for r in row..row+thickness_top {
        for c in column..column+thickness_left {
          push_tile (r, c, border.top_left);
        }
        for c in column+thickness_left..column+columns-thickness_right {
          push_tile (r, c, border.top);
        }
        for c in column+columns-thickness_right..column+columns {
          push_tile (r, c, border.top_right);
        }
      }
      // sides
      for r in row+thickness_top..row+rows-thickness_bottom {
        for c in column..column+thickness_left {
          push_tile (r, c, border.left);
        }
        for c in column+columns-thickness_right..column+columns {
          push_tile (r, c, border.right);
        }
      }
      // bottom
      for r in row+rows-thickness_bottom..row+rows {
        for c in column..column+thickness_left {
          push_tile (r, c, border.bottom_left);
        }
        for c in column+thickness_left..column+columns-thickness_right {
          push_tile (r, c, border.bottom);
        }
        for c in column+columns-thickness_right..column+columns {
          push_tile (r, c, border.bottom_right);
        }
      }
    }
  }
  tiles
}

/// Generate canvas body tiles
fn canvas_body (
  canvas : &view::component::Canvas,
  body   : &view::component::Body,
  style  : &view::Style
) -> Vec <gl::vertex::Vert2dTileColor> {
  use color::Normalize;
  use gl::vertex::Vert2dTileColor;
  let (fg, bg) = {
    let fg = style.fg.raw().normalize().into();
    let bg = style.bg.raw().normalize().into();
    (fg, bg)
  };
  let mut tiles = vec![];
  let (rc_min, _rc_max) = {
    let [row, col]   = [
      canvas.coordinates.position_vertical(),
      canvas.coordinates.position_horizontal()
    ];
    let [rows, cols] = [
      canvas.coordinates.dimensions_vertical()   as i32,
      canvas.coordinates.dimensions_horizontal() as i32
    ];
    ( (row, col),
      (row + rows-1, col + cols-1) )
  };
  let (border_thickness_top, border_thickness_left) =
    canvas.border.as_ref().map (|border|
      ( border.thickness_top  as i32,
        border.thickness_left as i32 )
    ).unwrap_or_default();
  for (i, line) in body.0.lines().enumerate() {
    let row = rc_min.0 + border_thickness_top + i as i32;
    for (j, ch) in line.chars().enumerate().skip_while (|(_, c)| *c == '\0') {
      let column = rc_min.1 + border_thickness_left + j as i32;
      tiles.push (Vert2dTileColor {
        row, column, tile: ch as u8, fg, bg
      });
    }
  }
  tiles
}

/// Generate canvas image rectangle vertex
fn canvas_rectangle_uv (
  canvas : &view::component::Canvas,
  image  : &view::component::Image
) -> gl::vertex::Vert2dRectUvLayer {
  use view::coordinates;
  let (_resource, layer) = match image {
    view::component::Image::Texture (view::Texture { resource, index }) =>
      (*resource, *index),
    _ => unimplemented!()
  };
  let (bottom_left, dimensions) = {
    let body_coordinates = canvas.body_coordinates();
    let horizontal = body_coordinates.position_horizontal();
    let vertical   = body_coordinates.position_vertical();
    let width      = body_coordinates.dimensions_horizontal();
    let height     = body_coordinates.dimensions_vertical();
    let ([x, y], [w, h]) = if body_coordinates.kind() == coordinates::Kind::Tile {
      let [tile_w, tile_h] = *coordinates::TILE_WH;
      ( coordinates::tile_to_pixel (vertical + height as i32, horizontal),
        [width * tile_w, height * tile_h]
      )
    } else {
      ([horizontal, vertical], [width, height])
    };
    ([x as f32, y as f32], [w as f32, h as f32])
  };
  // TODO: uv offset
  let uv = [0.0, 0.0];
  gl::vertex::Vert2dRectUvLayer { bottom_left, dimensions, uv, layer }
}