From db9704193697fc065a3672cb8e978dec4aee8975 Mon Sep 17 00:00:00 2001 From: Malik Olivier Boussejra Date: Thu, 29 Mar 2018 22:32:52 +0900 Subject: [PATCH] window_draw_list.rs: Basic structure to wrap ImGui's draw API This patch makes the basic structure for a wrapper around Dear ImGui's drawing API. 1. Implement `with_window_draw_list` method on Ui. Call this method to get access to the `WindowDrawList` object. This object holds the methods to access ImGui's drawing API. 2. Dear ImGui uses the ImU32 (an unsigned c_int) to represent colors in the drawing API. This commit wraps this type with ImColor for convenience. Any color representation (3or4-tuples, 3or4-arrays, ImU32 or ImVec4) can be converted into ImColor for convenience. 3. Difference between WindowDrawList and ChannelsSplit: Most drawing functions can be called on WindowDrawList and ChannelsSplit objects. However for safety, some functions can only be called on WindowDrawList or ChannelsSplit instance. For example `channels_set_current` can only be called after channels have been split. To avoid code duplication, functions common to WindowDrawList and ChannelsSplit are implemented within the `impl_draw_list_methods` macro. 4. Implement drawing functions (in this commit, add_line only). Calling `add_line` returns a default representation of the line to be drawn, but does not draw it. Then parameters, such as thickness, can be set. You must call `build` to draw the line. All drawing functions will be implemented following this pattern. --- src/lib.rs | 27 ++++++ src/window_draw_list.rs | 179 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 src/window_draw_list.rs diff --git a/src/lib.rs b/src/lib.rs index 3c54b61..40e7833 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub use string::{ImStr, ImString}; pub use style::StyleVar; pub use trees::{CollapsingHeader, TreeNode}; pub use window::Window; +pub use window_draw_list::{ImColor, WindowDrawList}; mod child_frame; mod color_editors; @@ -41,6 +42,7 @@ mod string; mod style; mod trees; mod window; +mod window_draw_list; pub struct ImGui { // We need to keep ownership of the ImStr values to ensure the *const char pointer @@ -1211,3 +1213,28 @@ impl<'ui> Ui<'ui> { unsafe { sys::igEndGroup(); } } } + +/// # Draw list for custom drawing +impl<'ui> Ui<'ui> { + /// Get access to drawing API + /// + /// # Examples + /// + /// ``` + /// fn custom_draw(ui: &Ui) { + /// ui.with_window_draw_list(|draw_list| { + /// // Draw a line + /// const WHITE: [f32; 3] = [1.0, 1.0, 1.0]; + /// draw_list.add_line([100.0, 100.0], [200.0, 200.0], WHITE).build(); + /// // Continue drawing ... + /// }); + /// } + /// ``` + pub fn with_window_draw_list(&self, f: F) + where + F: FnOnce(&WindowDrawList), + { + let window_draw_list = WindowDrawList::new(self); + f(&window_draw_list); + } +} diff --git a/src/window_draw_list.rs b/src/window_draw_list.rs new file mode 100644 index 0000000..6ea5841 --- /dev/null +++ b/src/window_draw_list.rs @@ -0,0 +1,179 @@ +use sys; +use sys::{ImDrawList, ImU32}; + +use super::{ImVec2, ImVec4, Ui}; + +use std::marker::PhantomData; + +/// Wrap `ImU32` (a type typically used by ImGui to store packed colors) +/// This type is used to represent the color of drawing primitives in ImGui's +/// custom drawing API. +/// +/// The type implements `From`, `From`, `From<[f32; 4]>`, +/// `From<[f32; 3]>`, `From<(f32, f32, f32, f32)>` and `From<(f32, f32, f32)>` +/// for convenience. If alpha is not provided, it is assumed to be 1.0 (255). +#[derive(Copy, Clone)] +pub struct ImColor(ImU32); + +impl From for ImU32 { + fn from(color: ImColor) -> Self { color.0 } +} + +impl From for ImColor { + fn from(color: ImU32) -> Self { ImColor(color) } +} + +impl From for ImColor { + fn from(v: ImVec4) -> Self { ImColor(unsafe { sys::igColorConvertFloat4ToU32(v) }) } +} + +impl From<[f32; 4]> for ImColor { + fn from(v: [f32; 4]) -> Self { ImColor(unsafe { sys::igColorConvertFloat4ToU32(v.into()) }) } +} + +impl From<(f32, f32, f32, f32)> for ImColor { + fn from(v: (f32, f32, f32, f32)) -> Self { + ImColor(unsafe { sys::igColorConvertFloat4ToU32(v.into()) }) + } +} + +impl From<[f32; 3]> for ImColor { + fn from(v: [f32; 3]) -> Self { [v[0], v[1], v[2], 1.0].into() } +} + +impl From<(f32, f32, f32)> for ImColor { + fn from(v: (f32, f32, f32)) -> Self { [v.0, v.1, v.2, 1.0].into() } +} + +/// All types from which ImGui's custom draw API can be used implement this +/// trait. This trait is internal to this library and implemented by +/// `WindowDrawList` and `ChannelsSplit`. +pub trait DrawAPI<'ui> { + /// Get draw_list object + fn draw_list(&self) -> *mut ImDrawList; +} + +/// Object implementing the custom draw API. +pub struct WindowDrawList<'ui> { + draw_list: *mut ImDrawList, + _phantom: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui> DrawAPI<'ui> for WindowDrawList<'ui> { + fn draw_list(&self) -> *mut ImDrawList { self.draw_list } +} + +impl<'ui> WindowDrawList<'ui> { + pub fn new(_: &Ui<'ui>) -> Self { + Self { + draw_list: unsafe { sys::igGetWindowDrawList() }, + _phantom: PhantomData, + } + } + + /// Split into *channels_count* drawing channels. + /// At the end of the closure, the channels are merged. The objects + /// are then drawn in the increasing order of their channel number, and not + /// in the all order they were called. + /// + /// # Example + /// + /// ``` + /// fn custom_drawing(ui: &Ui) { + /// ui.with_window_draw_list(|draw_list| { + /// draw_list.channels_split(2, |draw_list| { + /// draw_list.channels_set_current(1); + /// // ... Draw channel 1 + /// draw_list.channels_set_current(0); + /// // ... Draw channel 0 + /// }); + /// }); + /// } + /// ``` + pub fn channels_split(&self, channels_count: u32, f: F) { + unsafe { sys::ImDrawList_ChannelsSplit(self.draw_list, channels_count as i32) }; + f(&ChannelsSplit(self)); + unsafe { sys::ImDrawList_ChannelsMerge(self.draw_list) }; + } +} + +/// Represent the drawing interface within a call to `channels_split`. +pub struct ChannelsSplit<'ui>(&'ui WindowDrawList<'ui>); + +impl<'ui> DrawAPI<'ui> for ChannelsSplit<'ui> { + fn draw_list(&self) -> *mut ImDrawList { self.0.draw_list } +} + +impl<'ui> ChannelsSplit<'ui> { + /// Change current channel + pub fn channels_set_current(&self, channel_index: u32) { + unsafe { sys::ImDrawList_ChannelsSetCurrent(self.draw_list(), channel_index as i32) }; + } +} + +macro_rules! impl_draw_list_methods { + ($T: ident) => { + impl<'ui> $T<'ui> + where + $T<'ui>: DrawAPI<'ui>, + { + /// Returns a line from point `p1` to `p2` with color `c`. + pub fn add_line(&self, p1: P1, p2: P2, c: C) -> Line<'ui, $T> + where + P1: Into, + P2: Into, + C: Into, + { + Line::new(self, p1, p2, c) + } + } + }; +} + +impl_draw_list_methods!(WindowDrawList); +impl_draw_list_methods!(ChannelsSplit); + +/// Represents a line about to be drawn +pub struct Line<'ui, D: 'ui> { + p1: ImVec2, + p2: ImVec2, + color: ImColor, + thickness: f32, + draw_list: &'ui D, +} + +impl<'ui, D: DrawAPI<'ui>> Line<'ui, D> { + fn new(draw_list: &'ui D, p1: P1, p2: P2, c: C) -> Self + where + P1: Into, + P2: Into, + C: Into, + { + Self { + p1: p1.into(), + p2: p2.into(), + color: c.into(), + thickness: 1.0, + draw_list, + } + } + + /// Set line's thickness (default to 1.0 pixel) + pub fn thickness(mut self, thickness: f32) -> Self { + self.thickness = thickness; + self + } + + /// Draw the line on the window + pub fn build(self) { + unsafe { + sys::ImDrawList_AddLine( + self.draw_list.draw_list(), + self.p1, + self.p2, + self.color.into(), + self.thickness, + ) + } + } +}