diff --git a/imgui-examples/examples/test_drawing_channels_split.rs b/imgui-examples/examples/test_drawing_channels_split.rs new file mode 100644 index 0000000..d38d8b8 --- /dev/null +++ b/imgui-examples/examples/test_drawing_channels_split.rs @@ -0,0 +1,47 @@ +extern crate glium; +extern crate imgui; +extern crate imgui_glium_renderer; + +mod support; + +const CLEAR_COLOR: [f32; 4] = [0.2, 0.2, 0.2, 1.0]; +const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; +const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0]; + +fn main() { + support::run( + "test_drawing_channels_split".to_owned(), + CLEAR_COLOR, + |ui| { + let draw_list = ui.get_window_draw_list(); + // Will draw channel 0 first, then channel 1, whatever the order of + // the calls in the code. + // + // Here, we draw a red line on channel 1 then a white circle on + // channel 0. As a result, the red line will always appear on top of + // the white circle. + draw_list.channels_split(2, |channels| { + const RADIUS: f32 = 100.0; + let canvas_pos = ui.get_cursor_screen_pos(); + channels.set_current(1); + draw_list + .add_line( + canvas_pos, + [canvas_pos.0 + RADIUS, canvas_pos.1 + RADIUS], + RED, + ) + .thickness(5.0) + .build(); + + channels.set_current(0); + let center = (canvas_pos.0 + RADIUS, canvas_pos.1 + RADIUS); + draw_list + .add_circle(center, RADIUS, WHITE) + .thickness(10.0) + .num_segments(50) + .build(); + }); + true + }, + ); +} diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index c9cc5c8..2c589f7 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -47,6 +47,7 @@ struct State { file_menu: FileMenuState, radio_button: i32, color_edit: ColorEditState, + custom_rendering: CustomRenderingState, } impl Default for State { @@ -95,6 +96,7 @@ impl Default for State { file_menu: Default::default(), radio_button: 0, color_edit: ColorEditState::default(), + custom_rendering: Default::default(), } } } @@ -155,6 +157,24 @@ impl Default for AutoResizeState { fn default() -> Self { AutoResizeState { lines: 10 } } } +struct CustomRenderingState { + sz: f32, + col: [f32; 3], + points: Vec<(f32, f32)>, + adding_line: bool, +} + +impl Default for CustomRenderingState { + fn default() -> Self { + CustomRenderingState { + sz: 36.0, + col: [1.0, 1.0, 0.4], + points: vec![], + adding_line: false, + } + } +} + const CLEAR_COLOR: [f32; 4] = [114.0 / 255.0, 144.0 / 255.0, 154.0 / 255.0, 1.0]; fn main() { @@ -239,6 +259,14 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { }); } + if state.show_app_custom_rendering { + show_example_app_custom_rendering( + ui, + &mut state.custom_rendering, + &mut state.show_app_custom_rendering, + ); + } + ui.window(im_str!("ImGui Demo")) .title_bar(!state.no_titlebar) .show_borders(!state.no_border) @@ -742,3 +770,244 @@ My title is the same as window 1, but my identifier is unique.", .position((100.0, 300.0), ImGuiCond::FirstUseEver) .build(|| ui.text("This window has a changing title")); } + +fn show_example_app_custom_rendering(ui: &Ui, state: &mut CustomRenderingState, opened: &mut bool) { + ui.window(im_str!("Example: Custom rendering")) + .size((350.0, 560.0), ImGuiCond::FirstUseEver) + .opened(opened) + .build(|| { + ui.text("Primitives"); + // TODO: Add DragFloat to change value of sz + ui.color_edit(im_str!("Color"), &mut state.col).build(); + let draw_list = ui.get_window_draw_list(); + let p = ui.get_cursor_screen_pos(); + let spacing = 8.0; + let mut y = p.1 + 4.0; + for n in 0..2 { + let mut x = p.0 + 4.0; + let thickness = if n == 0 { 1.0 } else { 4.0 }; + draw_list + .add_circle( + (x + state.sz * 0.5, y + state.sz * 0.5), + state.sz * 0.5, + state.col, + ) + .num_segments(20) + .thickness(thickness) + .build(); + x += state.sz + spacing; + draw_list + .add_rect((x, y), (x + state.sz, y + state.sz), state.col) + .thickness(thickness) + .build(); + x += state.sz + spacing; + draw_list + .add_rect((x, y), (x + state.sz, y + state.sz), state.col) + .thickness(thickness) + .rounding(10.0) + .build(); + x += state.sz + spacing; + draw_list + .add_rect((x, y), (x + state.sz, y + state.sz), state.col) + .thickness(thickness) + .rounding(10.0) + .round_top_right(false) + .round_bot_left(false) + .build(); + x += state.sz + spacing; + draw_list + .add_triangle( + (x + state.sz * 0.5, y), + (x + state.sz, y + state.sz - 0.5), + (x, y + state.sz - 0.5), + state.col, + ) + .thickness(thickness) + .build(); + x += state.sz + spacing; + draw_list + .add_line((x, y), (x + state.sz, y), state.col) + .thickness(thickness) + .build(); + x += state.sz + spacing; + draw_list + .add_line((x, y), (x + state.sz, y + state.sz), state.col) + .thickness(thickness) + .build(); + x += state.sz + spacing; + draw_list + .add_line((x, y), (x, y + state.sz), state.col) + .thickness(thickness) + .build(); + x += spacing; + draw_list + .add_bezier_curve( + (x, y), + (x + state.sz * 1.3, y + state.sz * 0.3), + (x + state.sz - state.sz * 1.3, y + state.sz - state.sz * 0.3), + (x + state.sz, y + state.sz), + state.col, + ) + .thickness(thickness) + .build(); + y += state.sz + spacing; + } + let mut x = p.0 + 4.0; + draw_list + .add_circle( + (x + state.sz * 0.5, y + state.sz * 0.5), + state.sz * 0.5, + state.col, + ) + .num_segments(32) + .filled(true) + .build(); + x += state.sz + spacing; + draw_list + .add_rect((x, y), (x + state.sz, y + state.sz), state.col) + .filled(true) + .build(); + x += state.sz + spacing; + draw_list + .add_rect((x, y), (x + state.sz, y + state.sz), state.col) + .filled(true) + .rounding(10.0) + .build(); + x += state.sz + spacing; + draw_list + .add_rect((x, y), (x + state.sz, y + state.sz), state.col) + .filled(true) + .rounding(10.0) + .round_top_right(false) + .round_bot_left(false) + .build(); + x += state.sz + spacing; + draw_list + .add_triangle( + (x + state.sz * 0.5, y), + (x + state.sz, y + state.sz - 0.5), + (x, y + state.sz - 0.5), + state.col, + ) + .filled(true) + .build(); + x += state.sz + spacing; + const MULTICOLOR_RECT_CORNER_COLOR1: [f32; 3] = [0.0, 0.0, 0.0]; + const MULTICOLOR_RECT_CORNER_COLOR2: [f32; 3] = [1.0, 0.0, 0.0]; + const MULTICOLOR_RECT_CORNER_COLOR3: [f32; 3] = [1.0, 1.0, 0.0]; + const MULTICOLOR_RECT_CORNER_COLOR4: [f32; 3] = [0.0, 1.0, 0.0]; + draw_list.add_rect_filled_multicolor( + (x, y), + (x + state.sz, y + state.sz), + MULTICOLOR_RECT_CORNER_COLOR1, + MULTICOLOR_RECT_CORNER_COLOR2, + MULTICOLOR_RECT_CORNER_COLOR3, + MULTICOLOR_RECT_CORNER_COLOR4, + ); + ui.dummy(((state.sz + spacing) * 8.0, (state.sz + spacing) * 3.0)); + ui.separator(); + + ui.text(im_str!("Canvas example")); + if ui.button(im_str!("Clear"), (0.0, 0.0)) { + state.points.clear(); + } + if state.points.len() >= 2 { + ui.same_line(0.0); + if ui.button(im_str!("Undo"), (0.0, 0.0)) { + state.points.pop(); + state.points.pop(); + } + } + ui.text(im_str!( + "Left-click and drag to add lines,\nRight-click to undo" + )); + // Here we are using InvisibleButton() as a convenience to + // 1) advance the cursor, and + // 2) allows us to use IsItemHovered() + // However you can draw directly and poll mouse/keyboard by + // yourself. You can manipulate the cursor using GetCursorPos() and + // SetCursorPos(). If you only use the ImDrawList API, you can + // notify the owner window of its extends by using + // SetCursorPos(max). + + // ImDrawList API uses screen coordinates! + let canvas_pos = ui.get_cursor_screen_pos(); + // Resize canvas to what's available + let mut canvas_size = ui.get_content_region_avail(); + if canvas_size.0 < 50.0 { + canvas_size.0 = 50.0; + } + if canvas_size.1 < 50.0 { + canvas_size.1 = 50.0; + } + const CANVAS_CORNER_COLOR1: [f32; 3] = [0.2, 0.2, 0.2]; + const CANVAS_CORNER_COLOR2: [f32; 3] = [0.2, 0.2, 0.24]; + const CANVAS_CORNER_COLOR3: [f32; 3] = [0.24, 0.24, 0.27]; + const CANVAS_CORNER_COLOR4: [f32; 3] = [0.2, 0.2, 0.24]; + draw_list.add_rect_filled_multicolor( + canvas_pos, + (canvas_pos.0 + canvas_size.0, canvas_pos.1 + canvas_size.1), + CANVAS_CORNER_COLOR1, + CANVAS_CORNER_COLOR2, + CANVAS_CORNER_COLOR3, + CANVAS_CORNER_COLOR4, + ); + const CANVAS_BORDER_COLOR: [f32; 3] = [1.0, 1.0, 1.0]; + draw_list + .add_rect( + canvas_pos, + (canvas_pos.0 + canvas_size.0, canvas_pos.1 + canvas_size.1), + CANVAS_BORDER_COLOR, + ) + .build(); + + let mut adding_preview = false; + ui.invisible_button(im_str!("canvas"), canvas_size); + let mouse_pos = ui.imgui().mouse_pos(); + let mouse_pos_in_canvas = (mouse_pos.0 - canvas_pos.0, mouse_pos.1 - canvas_pos.1); + if state.adding_line { + adding_preview = true; + state.points.push(mouse_pos_in_canvas); + if !ui.imgui().is_mouse_down(ImMouseButton::Left) { + state.adding_line = false; + adding_preview = false; + } + } + if ui.is_item_hovered() { + if !state.adding_line && ui.imgui().is_mouse_clicked(ImMouseButton::Left) { + state.points.push(mouse_pos_in_canvas); + state.adding_line = true; + } + if ui.imgui().is_mouse_clicked(ImMouseButton::Right) && !state.points.is_empty() { + state.adding_line = false; + adding_preview = false; + state.points.pop(); + state.points.pop(); + } + } + draw_list.with_clip_rect_intersect( + canvas_pos, + (canvas_pos.0 + canvas_size.0, canvas_pos.1 + canvas_size.1), + || { + const LINE_COLOR: [f32; 3] = [1.0, 1.0, 0.0]; + for line in state.points.chunks(2) { + if line.len() < 2 { + break; + } + let (p1, p2) = (line[0], line[1]); + draw_list + .add_line( + (canvas_pos.0 + p1.0, canvas_pos.1 + p1.1), + (canvas_pos.0 + p2.0, canvas_pos.1 + p2.1), + LINE_COLOR, + ) + .thickness(2.0) + .build(); + } + }, + ); + if adding_preview { + state.points.pop(); + } + }); +} diff --git a/imgui-sys/src/lib.rs b/imgui-sys/src/lib.rs index 5908569..3d0f6a1 100644 --- a/imgui-sys/src/lib.rs +++ b/imgui-sys/src/lib.rs @@ -325,6 +325,26 @@ bitflags!( } ); +bitflags!( + /// Flags for indictating which corner of a rectangle should be rounded + #[repr(C)] + pub struct ImDrawCornerFlags: c_int { + const TopLeft = 1 << 0; + const TopRight = 1 << 1; + const BotRight = 1 << 2; + const BotLeft = 1 << 3; + const Top = ImDrawCornerFlags::TopLeft.bits + | ImDrawCornerFlags::TopRight.bits; + const Bot = ImDrawCornerFlags::BotLeft.bits + | ImDrawCornerFlags::BotRight.bits; + const Left = ImDrawCornerFlags::TopLeft.bits + | ImDrawCornerFlags::BotLeft.bits; + const Right = ImDrawCornerFlags::TopRight.bits + | ImDrawCornerFlags::BotRight.bits; + const All = 0xF; + } +); + pub type ImGuiTextEditCallback = Option< extern "C" fn(data: *mut ImGuiTextEditCallbackData) -> c_int, >; @@ -1840,7 +1860,7 @@ extern "C" { b: ImVec2, col: ImU32, rounding: c_float, - rounding_corners_flags: c_int, + rounding_corners_flags: ImDrawCornerFlags, thickness: c_float, ); pub fn ImDrawList_AddRectFilled( @@ -1849,7 +1869,7 @@ extern "C" { b: ImVec2, col: ImU32, rounding: c_float, - rounding_corners_flags: c_int, + rounding_corners_flags: ImDrawCornerFlags, ); pub fn ImDrawList_AddRectFilledMultiColor( list: *mut ImDrawList, @@ -1898,6 +1918,7 @@ extern "C" { radius: c_float, col: ImU32, num_segments: c_int, + thickness: c_float, ); pub fn ImDrawList_AddCircleFilled( list: *mut ImDrawList, diff --git a/src/lib.rs b/src/lib.rs index 3c54b61..f97a91b 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, ChannelsSplit}; 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 @@ -84,6 +86,16 @@ pub fn get_version() -> &'static str { } } +/// Represents one of the buttons of the mouse +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ImMouseButton { + Left = 0, + Right = 1, + Middle = 2, + Extra1 = 3, + Extra2 = 4, +} + impl ImGui { pub fn init() -> ImGui { ImGui { @@ -229,6 +241,25 @@ impl ImGui { sys::igGetMouseCursor() } } + /// Returns `true` if mouse is currently dragging with the `button` provided + /// as argument. + pub fn is_mouse_dragging(&self, button: ImMouseButton) -> bool { + unsafe { + sys::igIsMouseDragging(button as c_int, -1.0) + } + } + /// Returns `true` if the `button` provided as argument is currently down. + pub fn is_mouse_down(&self, button: ImMouseButton) -> bool { + unsafe { + sys::igIsMouseDown(button as c_int) + } + } + /// Returns `true` if the `button` provided as argument is being clicked. + pub fn is_mouse_clicked(&self, button: ImMouseButton) -> bool { + unsafe { + sys::igIsMouseClicked(button as c_int, false) + } + } pub fn key_ctrl(&self) -> bool { let io = self.io(); io.key_ctrl @@ -467,6 +498,13 @@ impl<'ui> Ui<'ui> { pub fn get_columns_count(&self) -> i32 { unsafe { sys::igGetColumnsCount() } } + /// Fill a space of `size` in pixels with nothing on the current window. + /// Can be used to move the cursor on the window. + pub fn dummy>(&self, size: S) { + let size = size.into(); + unsafe { sys::igDummy(&size) } + } + /// Get cursor position on the screen, in screen coordinates. /// This sets the point on which the next widget will be drawn. /// @@ -500,6 +538,16 @@ impl<'ui> Ui<'ui> { pub fn set_cursor_pos>(&self, pos: P) { unsafe { sys::igSetCursorPos(pos.into()) } } + + /// Get available space left between the cursor and the edges of the current + /// window. + pub fn get_content_region_avail(&self) -> (f32, f32) { + let mut out = ImVec2::new(0.0, 0.0); + unsafe { + sys::igGetContentRegionAvail(&mut out); + } + (out.x, out.y) + } } // ID scopes @@ -573,6 +621,11 @@ impl<'ui> Ui<'ui> { pub fn small_button<'p>(&self, label: &'p ImStr) -> bool { unsafe { sys::igSmallButton(label.as_ptr()) } } + /// Make a invisible event. Can be used to conveniently catch events when + /// mouse hovers or click the area covered by this invisible button. + pub fn invisible_button<'p, S: Into>(&self, label: &'p ImStr, size: S) -> bool { + unsafe { sys::igInvisibleButton(label.as_ptr(), size.into()) } + } pub fn checkbox<'p>(&self, label: &'p ImStr, value: &'p mut bool) -> bool { unsafe { sys::igCheckbox(label.as_ptr(), value) } } @@ -1211,3 +1264,39 @@ impl<'ui> Ui<'ui> { unsafe { sys::igEndGroup(); } } } + +/// # Draw list for custom drawing +impl<'ui> Ui<'ui> { + /// Get access to drawing API + /// + /// # Examples + /// + /// ```rust,no_run + /// # use imgui::*; + /// fn custom_draw(ui: &Ui) { + /// let draw_list = ui.get_window_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 ... + /// } + /// ``` + /// + /// This function will panic if several instances of [`WindowDrawList`] + /// coexist. Before a new instance is got, a previous instance should be + /// dropped. + /// + /// ```rust + /// # use imgui::*; + /// fn custom_draw(ui: &Ui) { + /// let draw_list = ui.get_window_draw_list(); + /// // Draw something... + /// + /// // This second call will panic! + /// let draw_list = ui.get_window_draw_list(); + /// } + /// ``` + pub fn get_window_draw_list(&'ui self) -> WindowDrawList<'ui> { + WindowDrawList::new(self) + } +} diff --git a/src/window_draw_list.rs b/src/window_draw_list.rs new file mode 100644 index 0000000..abc6a53 --- /dev/null +++ b/src/window_draw_list.rs @@ -0,0 +1,624 @@ +use sys; +use sys::{ImDrawCornerFlags, 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, Eq, PartialEq, Debug)] +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() } +} + +/// Object implementing the custom draw API. +/// +/// Called from [`Ui::get_window_draw_list`]. No more than one instance of this +/// structure can live in a program at the same time. +/// The program will panic on creating a second instance. +pub struct WindowDrawList<'ui> { + draw_list: *mut ImDrawList, + _phantom: PhantomData<&'ui Ui<'ui>>, +} + +static mut WINDOW_DRAW_LIST_LOADED: bool = false; + +impl<'ui> Drop for WindowDrawList<'ui> { + fn drop(&mut self) { + unsafe { WINDOW_DRAW_LIST_LOADED = false; } + } +} + +impl<'ui> WindowDrawList<'ui> { + pub(crate) fn new(_: &Ui<'ui>) -> Self { + unsafe { + if WINDOW_DRAW_LIST_LOADED { + panic!("WindowDrawList is already loaded! You can only load one instance of it!") + } + WINDOW_DRAW_LIST_LOADED = true; + } + 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 order they were called. + /// + /// # Example + /// + /// ```rust,no_run + /// # use imgui::*; + /// fn custom_drawing(ui: &Ui) { + /// let draw_list = ui.get_window_draw_list(); + /// draw_list.channels_split(2, |channels| { + /// channels.set_current(1); + /// // ... Draw channel 1 + /// 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 { + draw_list: self, + channels_count, + }); + unsafe { sys::ImDrawList_ChannelsMerge(self.draw_list) }; + } +} + +/// Represent the drawing interface within a call to [`channels_split`]. +/// +/// [`channels_split`]: WindowDrawList::channels_split +pub struct ChannelsSplit<'ui> { + draw_list: &'ui WindowDrawList<'ui>, + channels_count: u32, +} + +impl<'ui> ChannelsSplit<'ui> { + /// Change current channel. + /// + /// Panic if channel_index overflows the number of channels. + pub fn set_current(&self, channel_index: u32) { + assert!( + channel_index < self.channels_count, + "Channel cannot be set! Provided channel index ({}) is higher than channel count ({}).", + channel_index, + self.channels_count + ); + unsafe { + sys::ImDrawList_ChannelsSetCurrent(self.draw_list.draw_list, channel_index as i32) + }; + } +} + +/// Drawing functions +impl<'ui> WindowDrawList<'ui> { + /// Returns a line from point `p1` to `p2` with color `c`. + pub fn add_line(&'ui self, p1: P1, p2: P2, c: C) -> Line<'ui> + where + P1: Into, + P2: Into, + C: Into, + { + Line::new(self, p1, p2, c) + } + + /// Returns a rectangle whose upper-left corner is at point `p1` + /// and lower-right corner is at point `p2`, with color `c`. + pub fn add_rect(&'ui self, p1: P1, p2: P2, c: C) -> Rect<'ui> + where + P1: Into, + P2: Into, + C: Into, + { + Rect::new(self, p1, p2, c) + } + + /// Draw a rectangle whose upper-left corner is at point `p1` + /// and lower-right corner is at point `p2`. + /// The remains parameters are the respective color of the corners + /// in the counter-clockwise starting from the upper-left corner + /// first. + pub fn add_rect_filled_multicolor( + &self, + p1: P1, + p2: P2, + col_upr_left: C1, + col_upr_right: C2, + col_bot_right: C3, + col_bot_left: C4, + ) where + P1: Into, + P2: Into, + C1: Into, + C2: Into, + C3: Into, + C4: Into, + { + unsafe { + sys::ImDrawList_AddRectFilledMultiColor( + self.draw_list, + p1.into(), + p2.into(), + col_upr_left.into().into(), + col_upr_right.into().into(), + col_bot_right.into().into(), + col_bot_left.into().into(), + ); + } + } + + /// Returns a triangle with the given 3 vertices `p1`, `p2` and `p3` + /// and color `c`. + pub fn add_triangle(&'ui self, p1: P1, p2: P2, p3: P3, c: C) -> Triangle<'ui> + where + P1: Into, + P2: Into, + P3: Into, + C: Into, + { + Triangle::new(self, p1, p2, p3, c) + } + + /// Returns a circle with the given `center`, `radius` and `color`. + pub fn add_circle(&'ui self, center: P, radius: f32, color: C) -> Circle<'ui> + where + P: Into, + C: Into, + { + Circle::new(self, center, radius, color) + } + + /// Returns a Bezier curve stretching from `pos0` to `pos1`, whose + /// curvature is defined by `cp0` and `cp1`. + pub fn add_bezier_curve( + &'ui self, + pos0: P1, + cp0: P2, + cp1: P3, + pos1: P4, + color: C, + ) -> BezierCurve<'ui> + where + P1: Into, + P2: Into, + P3: Into, + P4: Into, + C: Into, + { + BezierCurve::new(self, pos0, cp0, cp1, pos1, color) + } + + /// Push a clipping rectangle on the stack, run `f` and pop it. + /// + /// Clip all drawings done within the closure `f` in the given + /// rectangle. + pub fn with_clip_rect(&self, min: P1, max: P2, f: F) + where + P1: Into, + P2: Into, + F: FnOnce(), + { + unsafe { sys::ImDrawList_PushClipRect(self.draw_list, min.into(), max.into(), false) } + f(); + unsafe { sys::ImDrawList_PopClipRect(self.draw_list) } + } + + /// Push a clipping rectangle on the stack, run `f` and pop it. + /// + /// Clip all drawings done within the closure `f` in the given + /// rectangle. Intersect with all clipping rectangle previously on + /// the stack. + pub fn with_clip_rect_intersect(&self, min: P1, max: P2, f: F) + where + P1: Into, + P2: Into, + F: FnOnce(), + { + unsafe { sys::ImDrawList_PushClipRect(self.draw_list, min.into(), max.into(), true) } + f(); + unsafe { sys::ImDrawList_PopClipRect(self.draw_list) } + } +} + +/// Represents a line about to be drawn +pub struct Line<'ui> { + p1: ImVec2, + p2: ImVec2, + color: ImColor, + thickness: f32, + draw_list: &'ui WindowDrawList<'ui>, +} + +impl<'ui> Line<'ui> { + fn new(draw_list: &'ui WindowDrawList, 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, + ) + } + } +} + +/// Represents a rectangle about to be drawn +pub struct Rect<'ui> { + p1: ImVec2, + p2: ImVec2, + color: ImColor, + rounding: f32, + flags: ImDrawCornerFlags, + thickness: f32, + filled: bool, + draw_list: &'ui WindowDrawList<'ui>, +} + +impl<'ui> Rect<'ui> { + fn new(draw_list: &'ui WindowDrawList, p1: P1, p2: P2, c: C) -> Self + where + P1: Into, + P2: Into, + C: Into, + { + Self { + p1: p1.into(), + p2: p2.into(), + color: c.into(), + rounding: 0.0, + flags: ImDrawCornerFlags::All, + thickness: 1.0, + filled: false, + draw_list, + } + } + + /// Set rectangle's corner rounding (default to 0.0: no rounding). + /// By default all corners are rounded if this value is set. + pub fn rounding(mut self, rounding: f32) -> Self { + self.rounding = rounding; + self + } + + /// Set flag to indicate if rectangle's top-left corner will be rounded. + pub fn round_top_left(mut self, value: bool) -> Self { + self.flags.set(ImDrawCornerFlags::TopLeft, value); + self + } + + /// Set flag to indicate if rectangle's top-right corner will be rounded. + pub fn round_top_right(mut self, value: bool) -> Self { + self.flags.set(ImDrawCornerFlags::TopRight, value); + self + } + + /// Set flag to indicate if rectangle's bottom-left corner will be rounded. + pub fn round_bot_left(mut self, value: bool) -> Self { + self.flags.set(ImDrawCornerFlags::BotLeft, value); + self + } + + /// Set flag to indicate if rectangle's bottom-right corner will be rounded. + pub fn round_bot_right(mut self, value: bool) -> Self { + self.flags.set(ImDrawCornerFlags::BotRight, value); + self + } + + /// Set rectangle's thickness (default to 1.0 pixel). + pub fn thickness(mut self, thickness: f32) -> Self { + self.thickness = thickness; + self + } + + /// Set to `true` to make a filled rectangle (default to `false`). + pub fn filled(mut self, filled: bool) -> Self { + self.filled = filled; + self + } + + /// Draw the rectangle on the window. + pub fn build(self) { + if self.filled { + unsafe { + sys::ImDrawList_AddRectFilled( + self.draw_list.draw_list, + self.p1, + self.p2, + self.color.into(), + self.rounding, + self.flags, + ); + } + } else { + unsafe { + sys::ImDrawList_AddRect( + self.draw_list.draw_list, + self.p1, + self.p2, + self.color.into(), + self.rounding, + self.flags, + self.thickness, + ); + } + } + } +} + +/// Represents a triangle about to be drawn on the window +pub struct Triangle<'ui> { + p1: ImVec2, + p2: ImVec2, + p3: ImVec2, + color: ImColor, + thickness: f32, + filled: bool, + draw_list: &'ui WindowDrawList<'ui>, +} + +impl<'ui> Triangle<'ui> { + fn new(draw_list: &'ui WindowDrawList, p1: P1, p2: P2, p3: P3, c: C) -> Self + where + P1: Into, + P2: Into, + P3: Into, + C: Into, + { + Self { + p1: p1.into(), + p2: p2.into(), + p3: p3.into(), + color: c.into(), + thickness: 1.0, + filled: false, + draw_list, + } + } + + /// Set triangle's thickness (default to 1.0 pixel) + pub fn thickness(mut self, thickness: f32) -> Self { + self.thickness = thickness; + self + } + + /// Set to `true` to make a filled triangle (default to `false`). + pub fn filled(mut self, filled: bool) -> Self { + self.filled = filled; + self + } + + /// Draw the triangle on the window. + pub fn build(self) { + if self.filled { + unsafe { + sys::ImDrawList_AddTriangleFilled( + self.draw_list.draw_list, + self.p1, + self.p2, + self.p3, + self.color.into(), + ) + } + } else { + unsafe { + sys::ImDrawList_AddTriangle( + self.draw_list.draw_list, + self.p1, + self.p2, + self.p3, + self.color.into(), + self.thickness, + ) + } + } + } +} + +/// Represents a circle about to be drawn +pub struct Circle<'ui> { + center: ImVec2, + radius: f32, + color: ImColor, + num_segments: u32, + thickness: f32, + filled: bool, + draw_list: &'ui WindowDrawList<'ui>, +} + +impl<'ui> Circle<'ui> { + pub fn new(draw_list: &'ui WindowDrawList, center: P, radius: f32, color: C) -> Self + where + P: Into, + C: Into, + { + Self { + center: center.into(), + radius, + color: color.into(), + num_segments: 12, + thickness: 1.0, + filled: false, + draw_list, + } + } + + /// Set number of segment used to draw the circle, default to 12. + /// Add more segments if you want a smoother circle. + pub fn num_segments(mut self, num_segments: u32) -> Self { + self.num_segments = num_segments; + self + } + + /// Set circle's thickness (default to 1.0 pixel) + pub fn thickness(mut self, thickness: f32) -> Self { + self.thickness = thickness; + self + } + + /// Set to `true` to make a filled circle (default to `false`). + pub fn filled(mut self, filled: bool) -> Self { + self.filled = filled; + self + } + + /// Draw the circle on the window. + pub fn build(self) { + if self.filled { + unsafe { + sys::ImDrawList_AddCircleFilled( + self.draw_list.draw_list, + self.center, + self.radius, + self.color.into(), + self.num_segments as i32, + ) + } + } else { + unsafe { + sys::ImDrawList_AddCircle( + self.draw_list.draw_list, + self.center, + self.radius, + self.color.into(), + self.num_segments as i32, + self.thickness, + ) + } + } + } +} + +/// Represents a Bezier curve about to be drawn +pub struct BezierCurve<'ui> { + pos0: ImVec2, + cp0: ImVec2, + pos1: ImVec2, + cp1: ImVec2, + color: ImColor, + thickness: f32, + /// If num_segments is not set, the bezier curve is auto-tessalated. + num_segments: Option, + draw_list: &'ui WindowDrawList<'ui>, +} + +impl<'ui> BezierCurve<'ui> { + fn new( + draw_list: &'ui WindowDrawList, + pos0: P1, + cp0: P2, + cp1: P3, + pos1: P4, + c: C, + ) -> Self + where + P1: Into, + P2: Into, + P3: Into, + P4: Into, + C: Into, + { + Self { + pos0: pos0.into(), + cp0: cp0.into(), + pos1: pos1.into(), + cp1: cp1.into(), + color: c.into(), + thickness: 1.0, + num_segments: None, + draw_list, + } + } + + /// Set curve's thickness (default to 1.0 pixel) + pub fn thickness(mut self, thickness: f32) -> Self { + self.thickness = thickness; + self + } + + /// Set number of segments used to draw the Bezier curve. If not set, the + /// bezier curve is auto-tessalated. + pub fn num_segments(mut self, num_segments: u32) -> Self { + self.num_segments = Some(num_segments); + self + } + + /// Draw the curve on the window. + pub fn build(self) { + unsafe { + sys::ImDrawList_AddBezierCurve( + self.draw_list.draw_list, + self.pos0, + self.cp0, + self.cp1, + self.pos1, + self.color.into(), + self.thickness, + self.num_segments.unwrap_or(0) as i32, + ) + } + } +}