mirror of
https://github.com/eliasstepanik/imgui-rs.git
synced 2026-01-13 22:48:34 +00:00
Merge pull request #111 from malikolivier/draw-api
Wraps the dear ImGui custom drawing API
This commit is contained in:
commit
544d7de930
47
imgui-examples/examples/test_drawing_channels_split.rs
Normal file
47
imgui-examples/examples/test_drawing_channels_split.rs
Normal file
@ -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
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
89
src/lib.rs
89
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<S: Into<ImVec2>>(&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<P: Into<ImVec2>>(&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<ImVec2>>(&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)
|
||||
}
|
||||
}
|
||||
|
||||
624
src/window_draw_list.rs
Normal file
624
src/window_draw_list.rs
Normal file
@ -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<ImU32>`, `From<ImVec4>`, `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<ImColor> for ImU32 {
|
||||
fn from(color: ImColor) -> Self { color.0 }
|
||||
}
|
||||
|
||||
impl From<ImU32> for ImColor {
|
||||
fn from(color: ImU32) -> Self { ImColor(color) }
|
||||
}
|
||||
|
||||
impl From<ImVec4> 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<F: FnOnce(&ChannelsSplit)>(&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<P1, P2, C>(&'ui self, p1: P1, p2: P2, c: C) -> Line<'ui>
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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<P1, P2, C>(&'ui self, p1: P1, p2: P2, c: C) -> Rect<'ui>
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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<P1, P2, C1, C2, C3, C4>(
|
||||
&self,
|
||||
p1: P1,
|
||||
p2: P2,
|
||||
col_upr_left: C1,
|
||||
col_upr_right: C2,
|
||||
col_bot_right: C3,
|
||||
col_bot_left: C4,
|
||||
) where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
C1: Into<ImColor>,
|
||||
C2: Into<ImColor>,
|
||||
C3: Into<ImColor>,
|
||||
C4: Into<ImColor>,
|
||||
{
|
||||
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<P1, P2, P3, C>(&'ui self, p1: P1, p2: P2, p3: P3, c: C) -> Triangle<'ui>
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
P3: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
Triangle::new(self, p1, p2, p3, c)
|
||||
}
|
||||
|
||||
/// Returns a circle with the given `center`, `radius` and `color`.
|
||||
pub fn add_circle<P, C>(&'ui self, center: P, radius: f32, color: C) -> Circle<'ui>
|
||||
where
|
||||
P: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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<P1, P2, P3, P4, C>(
|
||||
&'ui self,
|
||||
pos0: P1,
|
||||
cp0: P2,
|
||||
cp1: P3,
|
||||
pos1: P4,
|
||||
color: C,
|
||||
) -> BezierCurve<'ui>
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
P3: Into<ImVec2>,
|
||||
P4: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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<P1, P2, F>(&self, min: P1, max: P2, f: F)
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
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<P1, P2, F>(&self, min: P1, max: P2, f: F)
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
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<P1, P2, C>(draw_list: &'ui WindowDrawList, p1: P1, p2: P2, c: C) -> Self
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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<P1, P2, C>(draw_list: &'ui WindowDrawList, p1: P1, p2: P2, c: C) -> Self
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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<P1, P2, P3, C>(draw_list: &'ui WindowDrawList, p1: P1, p2: P2, p3: P3, c: C) -> Self
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
P3: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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<P, C>(draw_list: &'ui WindowDrawList, center: P, radius: f32, color: C) -> Self
|
||||
where
|
||||
P: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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<u32>,
|
||||
draw_list: &'ui WindowDrawList<'ui>,
|
||||
}
|
||||
|
||||
impl<'ui> BezierCurve<'ui> {
|
||||
fn new<P1, P2, P3, P4, C>(
|
||||
draw_list: &'ui WindowDrawList,
|
||||
pos0: P1,
|
||||
cp0: P2,
|
||||
cp1: P3,
|
||||
pos1: P4,
|
||||
c: C,
|
||||
) -> Self
|
||||
where
|
||||
P1: Into<ImVec2>,
|
||||
P2: Into<ImVec2>,
|
||||
P3: Into<ImVec2>,
|
||||
P4: Into<ImVec2>,
|
||||
C: Into<ImColor>,
|
||||
{
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user