aboutsummaryrefslogtreecommitdiff
path: root/src/client/gui
diff options
context:
space:
mode:
authorArne Dußin2021-01-27 14:01:50 +0100
committerArne Dußin2021-02-02 22:16:15 +0100
commitf92e9f6f07b1e3834c2ca58ce3510734819d08e4 (patch)
tree20e3d3afce342a56ae98f6c20491482ccd2b5c6b /src/client/gui
parentc60a6d07efb120724b308e29e8e70f27c87c952d (diff)
downloadgraf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.tar.gz
graf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.zip
Rework graf karto to fit the client/server structure
Diffstat (limited to 'src/client/gui')
-rw-r--r--src/client/gui/decimal_num_box.rs173
-rw-r--r--src/client/gui/dimension_indicator.rs308
-rw-r--r--src/client/gui/mod.rs18
-rw-r--r--src/client/gui/position_indicator.rs37
-rw-r--r--src/client/gui/tool_sidebar.rs91
5 files changed, 627 insertions, 0 deletions
diff --git a/src/client/gui/decimal_num_box.rs b/src/client/gui/decimal_num_box.rs
new file mode 100644
index 0000000..e9395f7
--- /dev/null
+++ b/src/client/gui/decimal_num_box.rs
@@ -0,0 +1,173 @@
+//! Functions similarly to a text-bux, but only accepts floating point (decimal) numbers
+//!
+//! Since a lot of functions require the user to input measurements in meters, it is useful to have a
+//! singular entity that reads these in an intuitive way. Inputting of such numbers is handled in
+//! this module.
+
+use crate::math::{self, Vec2};
+use nalgebra::RealField;
+use num_traits::Pow;
+use raylib::drawing::RaylibDraw;
+use raylib::ffi::{Color, KeyboardKey};
+use raylib::text;
+use raylib::RaylibHandle;
+use std::str::FromStr;
+
+/// The number of decimal places that can be edited and will be shown by a decimal text field.
+pub const DECIMAL_PLACES: u16 = 4;
+
+/// The decimal num box can handle any decimal number, like f32 or f64. Currently has a hard limit
+/// of four decimal places, but that may change in the future.
+pub struct DecimalNumBox<F: RealField + Pow<u16, Output = F> + FromStr> {
+ input: String,
+ last_value: F,
+ active: bool,
+}
+
+impl<F: RealField + Pow<u16, Output = F> + FromStr> DecimalNumBox<F> {
+ /// Create a new Number box showing the value specified. Should the value have more then the
+ /// maximum number of decimal places, it will be rounded.
+ pub fn new(value: F) -> Self {
+ let value = math::round_nth_decimal(value, DECIMAL_PLACES);
+ let input = format!("{:.4}", value);
+
+ Self {
+ input,
+ last_value: value,
+ active: false,
+ }
+ }
+
+ /// Get the value entered by the user. If the user has something that cannot be parsed into a
+ /// decimal value, this differs from the string that is shown and is instead the last value
+ /// entered by the user that is still a valid decimal number.
+ pub fn value(&self) -> F {
+ self.last_value
+ }
+
+ /// Set the value directly. This may only be done, if the box is currently not active, to protect
+ /// user input. Returns true if the value could be set, otherwise false.
+ pub fn set_value(&mut self, value: F) -> bool {
+ if !self.active {
+ self.last_value = math::round_nth_decimal(value, DECIMAL_PLACES);
+ // XXX: Don't use the magical 4
+ self.input = format!("{:.4}", self.last_value);
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Check if this number box is currently active. Active means, it's capturing keyboard input.
+ /// If it's not active, it does not attempt to capture any keystrokes.
+ pub fn active(&self) -> bool {
+ self.active
+ }
+
+ /// Set if the box is active (capturing keyboard input and adjusting it's value accordingly) or
+ /// not.
+ pub fn set_active(&mut self, active: bool) {
+ self.active = active
+ }
+
+ /// Update this decimal box. If it is inactive, this doesn't do anything, but if it is active, it
+ /// captures the keyboard input, if available. Returns `true`, if the value changed, otherwise
+ /// `false`. Note that the string that is displayed may change, but the value does not have to.
+ /// This happens, if the user types something invalid. In this case, `false` is returned as well.
+ pub fn update(&mut self, rl: &mut RaylibHandle) -> bool {
+ /* If the box is currently inactive, nothing must be changed, and this function will do
+ * nothing.
+ */
+ if !self.active {
+ return false;
+ }
+
+ // TODO: Check for movement keys.
+
+ // Delete the last character when pressing backspace.
+ let string_changed = if rl.is_key_pressed(KeyboardKey::KEY_BACKSPACE) {
+ self.input.pop().is_some()
+ }
+ // Check the entered numbers or decimal point.
+ else if let Some(key) = rl.get_key_pressed() {
+ match key {
+ // Add (at most one) decimal point to the input when entering a dot.
+ KeyboardKey::KEY_PERIOD => {
+ if !self.input.contains('.') {
+ self.input.push('.');
+ true
+ } else {
+ false
+ }
+ }
+ _ => {
+ if key as u16 >= KeyboardKey::KEY_ZERO as u16
+ && key as u16 <= KeyboardKey::KEY_NINE as u16
+ {
+ self.input.push(key as u8 as char);
+ true
+ } else {
+ false
+ }
+ }
+ }
+ } else {
+ false
+ };
+
+ if string_changed {
+ // Try to parse the new string. If it doesn't work, keep the old one.
+ match self.input.parse::<F>() {
+ Ok(value) => {
+ let value = math::round_nth_decimal(value, DECIMAL_PLACES);
+ if value != self.last_value {
+ self.last_value = value;
+ true
+ } else {
+ false
+ }
+ }
+ Err(_) => false,
+ }
+ } else {
+ false
+ }
+ }
+
+ /// Draw the number box at the given position. the `unit` parameter is used to append this text,
+ /// let's say for instance 'm' for meters to the text drawn to screen. Most of the time, a unit
+ /// makes sense to show on this number box, otherwise it can be left as an empty string. The unit
+ /// has no relevance to internal processes and cannot be edited by the user.
+ pub fn draw(&self, rld: &mut impl RaylibDraw, unit: &str, pos: &Vec2<f64>) {
+ let text = format!("{}{}", self.input, unit);
+ let width = text::measure_text(&text, 20);
+
+ // Draw background to highlight this box if it's active.
+ if self.active {
+ rld.draw_rectangle_v(
+ *pos - Vec2::new(5., 5.),
+ Vec2::new(width as f32 + 10., 20. + 10.),
+ Color {
+ r: 120,
+ g: 120,
+ b: 120,
+ a: 180,
+ },
+ );
+ }
+
+ // Draw the text of the box.
+ rld.draw_text(
+ &text,
+ pos.x as i32,
+ pos.y as i32,
+ 20,
+ Color {
+ r: 255,
+ g: 255,
+ b: 255,
+ a: 255,
+ },
+ )
+ }
+}
diff --git a/src/client/gui/dimension_indicator.rs b/src/client/gui/dimension_indicator.rs
new file mode 100644
index 0000000..fb6394a
--- /dev/null
+++ b/src/client/gui/dimension_indicator.rs
@@ -0,0 +1,308 @@
+//! An interface element that shows the size of the selected map items and provides a means to
+//! manually change the size of them in a precise manner should need be.
+
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::map::Map;
+use crate::client::transform::Transform;
+use crate::client::Editor;
+use crate::math::{self, Rect, Vec2};
+use crate::net::Cargo;
+use nalgebra::{Matrix3, Vector2};
+use raylib::drawing::RaylibDraw;
+use raylib::ffi::KeyboardKey;
+use raylib::RaylibHandle;
+
+/// A state the [DimensionIndicator] is currently in. This determines the behaviour of it and what
+/// inputs it might be waiting for.
+enum State {
+ /// In this state, the indicator is not trying to read any keyboard input, but will instead watch
+ /// for any changes to the dimensions from a different source, updating its display.
+ Watching,
+ /// In this state, the indicator will capture keyboard input and attempt to set the dimensions
+ /// according to whatever was entered. If the dimensions cannot be set, the indicator will use
+ /// the last valid dimensions.
+ Ruling {
+ dim_x: String,
+ dim_y: String,
+ editing_x: bool,
+ },
+}
+
+/// Used to render the horizontal and vertical dimensions of whatever is selected on the map and, if
+/// the user so desires edit them directly by entering values into it.
+pub struct DimensionIndicator {
+ /// The [State] the dimension indicator is currently in.
+ state: State,
+ /// The last dimensions that were valid.
+ bounds: Rect<f64>,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self::Watching
+ }
+}
+
+impl Default for DimensionIndicator {
+ fn default() -> Self {
+ Self {
+ state: State::default(),
+ bounds: Rect::new(0., 0., 0., 0.),
+ }
+ }
+}
+
+impl DimensionIndicator {
+ /// Create a new dimension indicator. While it is possible to have multiple instances, this is
+ /// not generally recommended, since they will need to be managed carefully or otherwise steal
+ /// keystrokes from each other.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Update whatever is selected on the map according to the dimension indicator rules and rulers.
+ pub fn update(&mut self, editor: &Editor, rl: &mut RaylibHandle) {
+ match self.state {
+ State::Watching => self.update_watching(editor.map(), rl),
+ State::Ruling { .. } => self.update_ruling(editor, rl),
+ };
+ }
+
+ fn update_watching(&mut self, map: &Map, rl: &RaylibHandle) {
+ let mut min: Vec2<f64> = Vec2::default();
+ let mut max: Vec2<f64> = Vec2::default();
+
+ /* Try to find selected items. If no items exist, the dimension indicator is set to its
+ * default, otherwise it is adjusted to the size of the combined selection.
+ */
+ let mut selection_exists = false;
+ for (_id, e) in map.elements() {
+ if e.selected() {
+ let element_bounds = e.as_component().bounding_rect();
+ if selection_exists {
+ // Adjust the currently detected selection size.
+ min.x = math::partial_min(min.x, element_bounds.x);
+ min.y = math::partial_min(min.y, element_bounds.y);
+ max.x = math::partial_max(max.x, element_bounds.x + element_bounds.w);
+ max.y = math::partial_max(max.y, element_bounds.y + element_bounds.h);
+ } else {
+ // No selection size detected yet. Set now.
+ min.x = element_bounds.x;
+ min.y = element_bounds.y;
+ max.x = element_bounds.x + element_bounds.w;
+ max.y = element_bounds.y + element_bounds.h;
+ }
+ selection_exists = true;
+ }
+ }
+
+ // Set the current selection limits, if any.
+ self.bounds = if selection_exists {
+ Rect::bounding_rect(min, max)
+ } else {
+ Rect::new(0., 0., 0., 0.)
+ };
+
+ // Check if the user wants to change into editing mode, which the user can only do if there
+ // is a selection to begin with.
+ if selection_exists && rl.is_key_pressed(KeyboardKey::KEY_TAB) {
+ self.state = State::Ruling {
+ dim_x: self.bounds.w.to_string(),
+ dim_y: self.bounds.h.to_string(),
+ editing_x: true,
+ };
+ }
+ }
+
+ fn update_ruling(&mut self, editor: &Editor, rl: &mut RaylibHandle) {
+ // Get the currently edited dimension for processing.
+ let (edited_dim, editing_x) = match &mut self.state {
+ State::Watching => panic!("Called ruler update when in watching state"),
+ State::Ruling {
+ dim_x,
+ dim_y,
+ editing_x,
+ } => {
+ if *editing_x {
+ (dim_x, editing_x)
+ } else {
+ (dim_y, editing_x)
+ }
+ }
+ };
+
+ // Switch the currently edited dimension when pressing tab.
+ if rl.is_key_pressed(KeyboardKey::KEY_TAB) {
+ *editing_x = !*editing_x;
+ return;
+ }
+ // Finish editing mode on enter.
+ if rl.is_key_pressed(KeyboardKey::KEY_ENTER) {
+ self.state = State::Watching;
+ return;
+ }
+
+ // Marker to see if the dimensions will have to be checked for an update.
+ let mut dimension_changed = false;
+ // Delete the last character of the dimension on backspace.
+ if rl.is_key_pressed(KeyboardKey::KEY_BACKSPACE) {
+ edited_dim.pop();
+ dimension_changed = true;
+ }
+ /* Capture the current key and try to add it to the string of the current dimension,
+ * if possible.
+ */
+ else if let Some(key) = rl.get_key_pressed() {
+ match key {
+ // Add a decimal point to the dimension if possible.
+ KeyboardKey::KEY_PERIOD => {
+ if !edited_dim.contains('.') {
+ edited_dim.push('.');
+ }
+ // Nothing changed here, since there is an implicit .0 at the end.
+ }
+ // Handle the entered key if it is a number to append it to the currently edited dimension.
+ _ => {
+ if key as u16 >= KeyboardKey::KEY_ZERO as u16
+ && key as u16 <= KeyboardKey::KEY_NINE as u16
+ {
+ edited_dim.push(key as u8 as char);
+ dimension_changed = true;
+ }
+ }
+ };
+ }
+
+ if dimension_changed {
+ /* Try to parse the dimension from the currently edited string. If it
+ * is valid, change the dimensions of the currently selected items. If
+ * not, ignore the change and wait for a valid dimension.
+ */
+ if let Ok(dim) = edited_dim.parse::<f64>() {
+ let new_bounds = if *editing_x {
+ Rect::new(self.bounds.x, self.bounds.y, dim, self.bounds.h)
+ } else {
+ Rect::new(self.bounds.x, self.bounds.y, self.bounds.h, dim)
+ };
+
+ self.set_bounds(editor, new_bounds);
+ }
+ }
+ }
+
+ /// Set the selection boundaries to the given bounds. Tries to transform the
+ /// currently selected items in the map so they fit inside of the new bounding box.
+ ///
+ /// # Panics
+ /// If the `bounds` have a negative value for width or height, the dimensions
+ /// cannot be set and the function will panic.
+ pub fn set_bounds(&mut self, editor: &Editor, bounds: Rect<f64>) {
+ if bounds.w <= 0. || bounds.h <= 0. {
+ panic!("Cannot set dimensions of elements to zero.");
+ }
+
+ // If the bounds are the same as before, there is nothing to do.
+ if self.bounds == bounds {
+ return;
+ }
+
+ /* Create a matrix to transform from the current rectangle bounds into the
+ * new bounds. Internally, this is a three-step process. First, we
+ * translate the points currently in the bounding box to the origin
+ * (0, 0) origin vector of the map, then scale and finally move it to the
+ * origin of the new rectangle. This needs to be applied to all vertices
+ * of all elements that can be scaled.
+ */
+ let scale = Vector2::new(bounds.w / self.bounds.w, bounds.h / self.bounds.h);
+ let transform = Matrix3::new_translation(&Vector2::new(-self.bounds.x, -self.bounds.y))
+ .append_nonuniform_scaling(&scale)
+ .append_translation(&Vector2::new(bounds.x, bounds.y));
+
+ for (id, element) in editor.map().elements() {
+ if element.selected() {
+ if element.as_component().as_non_rigid().is_some() {
+ editor
+ .server()
+ .send(Cargo::ApplyMatrix((id, transform.clone())));
+ }
+ }
+ }
+
+ self.bounds = bounds;
+ }
+
+ /// Draw the dimensions detected on the current selection.
+ pub fn draw(&self, rld: &mut impl RaylibDraw, transform: &Transform) {
+ /* Ignore a selection that has no non-null dimensions, since this usually
+ * indicates that there is nothing to be scaled.
+ */
+ if self.bounds.w == 0. && self.bounds.h == 0. {
+ return;
+ }
+
+ let (dim_str_width, dim_str_height) = match &self.state {
+ State::Watching => (self.bounds.w.to_string(), self.bounds.h.to_string()),
+ State::Ruling { dim_x, dim_y, .. } => (dim_x.clone(), dim_y.clone()),
+ };
+
+ // Draw the horizontal dimension at the bottom and the vertical dimension to the right.
+ // Use the valid dimensions, but show the edited dimensions in the String (should they differ)
+ let top_right = Vec2::new(self.bounds.x + self.bounds.w, self.bounds.y);
+ let bot_left = Vec2::new(self.bounds.x, self.bounds.y + self.bounds.h);
+ let bot_right = Vec2::new(self.bounds.x + self.bounds.w, self.bounds.y + self.bounds.h);
+ let dimensions = [
+ (bot_left, bot_right, dim_str_width),
+ (bot_right, top_right, dim_str_height),
+ ];
+ for (start, end, dim_str) in &dimensions {
+ // Don't draw anything if the length is zero.
+ if start == end {
+ continue;
+ }
+
+ /* Get the vector that is perpendicular and points right/down from the line, assuming
+ * the lines prefer left as start over right and bottom over top.
+ */
+ let line_normal = {
+ // Start with the direction of the line vector.
+ let dir = *start - *end;
+ // Calculate perpendicular vec and normalise.
+ dir.rotated_90_clockwise() / dir.length()
+ };
+
+ // To not have the line directly in the rect, move start and end outside a bit.
+ let start_px = transform.point_m_to_px(start) + line_normal * 10.;
+ let end_px = transform.point_m_to_px(end) + line_normal * 10.;
+
+ /* Draw the indicator line, with stubs at both ends. */
+ // First the two stubs.
+ rld.draw_line_ex(
+ start_px - line_normal * 5.,
+ start_px + line_normal * 5.,
+ 2.,
+ DEFAULT_COLOURS.dimension_indicators,
+ );
+ rld.draw_line_ex(
+ end_px - line_normal * 5.,
+ end_px + line_normal * 5.,
+ 2.,
+ DEFAULT_COLOURS.dimension_indicators,
+ );
+ // Then the actual indicator line.
+ rld.draw_line_ex(start_px, end_px, 2., DEFAULT_COLOURS.dimension_indicators);
+
+ /* Draw the indicator text showing how long this line is in meters.
+ * It should be placed in the middle of the line, but not into the line directly, so it
+ * will be moved out by the normal.
+ */
+ let text_pos = transform.point_m_to_px(&((*end + *start) / 2.)) + line_normal * 20.;
+ rld.draw_text(
+ &format!("{}m", dim_str),
+ text_pos.x as i32,
+ text_pos.y as i32,
+ 20,
+ DEFAULT_COLOURS.dimension_text,
+ );
+ }
+ }
+}
diff --git a/src/client/gui/mod.rs b/src/client/gui/mod.rs
new file mode 100644
index 0000000..62173ec
--- /dev/null
+++ b/src/client/gui/mod.rs
@@ -0,0 +1,18 @@
+//! General graphical user interfaces
+//!
+//! This mod does not contain all graphical content on screen, but all user interfaces that is drawn
+//! that is not contained in a different category. This means all interface elements where it does not
+//! make sense to bind it to any other part of the program, for instance a tool or type of element.
+//! It also does *not* contain anything that does anything that is not triggered by the user, which
+//! means everything is called top-down from this module. A function in this module should not be
+//! called from any point in the program except the main loop, where the user input is polled.
+
+pub mod decimal_num_box;
+pub mod dimension_indicator;
+pub mod position_indicator;
+pub mod tool_sidebar;
+
+pub use self::decimal_num_box::*;
+pub use self::dimension_indicator::*;
+pub use self::position_indicator::*;
+pub use self::tool_sidebar::*;
diff --git a/src/client/gui/position_indicator.rs b/src/client/gui/position_indicator.rs
new file mode 100644
index 0000000..4d68b86
--- /dev/null
+++ b/src/client/gui/position_indicator.rs
@@ -0,0 +1,37 @@
+//! The position indicator shows the mouse position on the map
+//!
+//! The exact position the mouse is currently on is shown unless hidden by the user (TODO). This
+//! helps to place things exactly where they should be on the map and let the user know where they
+//! are looking and where relative to them other things should be easily at all times. Currently, this
+//! is a simple HUD so it doesn't interact with anything in the world, but that may change in the
+//! future.
+
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::snapping::Snapper;
+use crate::client::transform::Transform;
+use crate::math::Vec2;
+use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
+
+/// Function to draw a dot at the mouse position and the coordinates associated with it.
+// TODO: Snap this, when the user wants to snap, don't if they don't want to.
+pub fn position_indicator_draw(
+ rld: &mut RaylibDrawHandle,
+ mouse_pos_px: Vec2<f64>,
+ transform: &Transform,
+ snapper: &Snapper,
+) {
+ let mouse_pos_snapped_m = snapper.snap(transform.point_px_to_m(&mouse_pos_px));
+ let mouse_pos_snapped_px = transform.point_m_to_px(&mouse_pos_snapped_m);
+
+ rld.draw_circle_v(mouse_pos_snapped_px, 2., DEFAULT_COLOURS.position_indicator);
+ rld.draw_text(
+ &format!(
+ "({:.3}m, {:.3}m)",
+ mouse_pos_snapped_m.x, mouse_pos_snapped_m.y
+ ),
+ mouse_pos_snapped_px.x as i32 - 30,
+ mouse_pos_snapped_px.y as i32 - 30,
+ 20,
+ DEFAULT_COLOURS.position_text,
+ );
+}
diff --git a/src/client/gui/tool_sidebar.rs b/src/client/gui/tool_sidebar.rs
new file mode 100644
index 0000000..3147bf8
--- /dev/null
+++ b/src/client/gui/tool_sidebar.rs
@@ -0,0 +1,91 @@
+//! The sidebar showing all tools available to the user. This toolbar handles changing the active tool
+//! based on the mouse input and (TODO!) keyboard inputs.
+// TODO: Currently, the keyboard shortcuts for tools are handled by the editor, but a lot speaks for
+// them being handled by the ToolSidebar instead.
+
+use crate::client::input::Input;
+use crate::client::tool::ToolType;
+use crate::client::Editor;
+use crate::math::Rect;
+use raylib::core::texture::Texture2D;
+use raylib::rgui::RaylibDrawGui;
+use raylib::{RaylibHandle, RaylibThread};
+use std::mem;
+
+/// The file containing textures for all buttons describing the tools.
+pub const BUTTON_FILE: &str = "assets/button/tool_buttons.png";
+
+/// Sidebar that renders and handles input for the tool activation buttons.
+pub struct ToolSidebar {
+ button_texture: Texture2D,
+ bindings_id: usize,
+ panel_rect: Rect<u16>,
+}
+
+impl ToolSidebar {
+ /// Create a new tool sidebar. There should be only one sidebar per program instance.
+ pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread, input: &mut Input) -> Self {
+ let button_texture = rl
+ .load_texture(rlt, BUTTON_FILE)
+ .expect("Could not read file containing tool icons.");
+
+ let panel_rect = Self::panel_rect(rl.get_screen_height() as u16);
+ let bindings_id = input.add_local_handler(panel_rect.clone());
+
+ Self {
+ button_texture,
+ bindings_id,
+ panel_rect,
+ }
+ }
+
+ fn panel_rect(screen_height: u16) -> Rect<u16> {
+ /* The width is currently hardcoded as 104, which is
+ * 64 (button-size) + 20 left gap + 20 right gap
+ */
+ Rect::new(0, 0, 104, screen_height)
+ }
+
+ /// Update the state of the tool sidebar. Due to raylib limitations, this is not where the tools
+ /// are selected for the editor, which happens in draw.
+ pub fn update(&mut self, screen_height: u16, input: &mut Input) {
+ let new_panel_rect = Self::panel_rect(screen_height);
+ if new_panel_rect != self.panel_rect {
+ self.panel_rect = new_panel_rect;
+ input.set_binding_rect(self.bindings_id, self.panel_rect);
+ }
+ }
+
+ /// Draw the tool buttons and encasing panel. Because of the way raylib works, this also handles
+ /// clicking on tool buttons, which may be changed in the future, should a different gui be
+ /// chosen.
+ pub fn draw(&self, rld: &mut impl RaylibDrawGui, editor: &mut Editor) {
+ rld.gui_panel(Rect::new(
+ self.panel_rect.x as f32,
+ self.panel_rect.y as f32,
+ self.panel_rect.w as f32,
+ self.panel_rect.h as f32,
+ ));
+
+ // TODO: Update to new input system. Create buttons that integrate.
+ let mut active = editor.active();
+ for i in 0..ToolType::NumTools as usize {
+ let is_current_active = active as usize == i;
+ if rld.gui_image_button_ex(
+ Rect::new(20., i as f32 * 100. + 20., 64., 64.),
+ None,
+ &self.button_texture,
+ Rect::new(
+ is_current_active as u8 as f32 * 64.,
+ i as f32 * 64.,
+ 64.,
+ 64.,
+ ),
+ ) {
+ active = unsafe { mem::transmute(i as u8) };
+ }
+ }
+
+ editor.set_active(active);
+ }
+}