use crate::*;
use std::cmp::{max, min};
use std::rc::Rc;
pub(crate) fn intersect_clip_rect(limit: Recti, rect: Recti) -> Recti {
rect.intersect(&limit).unwrap_or_default()
}
pub(crate) fn clip_relation(bounds: Recti, clip: Recti) -> Clip {
if bounds.width <= 0 || bounds.height <= 0 || clip.width <= 0 || clip.height <= 0 {
return Clip::All;
}
if bounds.x > clip.x + clip.width || bounds.x + bounds.width < clip.x || bounds.y > clip.y + clip.height || bounds.y + bounds.height < clip.y {
return Clip::All;
}
if bounds.x >= clip.x && bounds.x + bounds.width <= clip.x + clip.width && bounds.y >= clip.y && bounds.y + bounds.height <= clip.y + clip.height {
return Clip::None;
}
Clip::Part
}
pub(crate) struct DrawCtx<'a> {
commands: &'a mut Vec<Command>,
triangle_vertices: &'a mut Vec<Vertex>,
clip_stack: &'a mut Vec<Recti>,
style: &'a Style,
atlas: &'a AtlasHandle,
}
pub(crate) fn baseline_aligned_top(rect: Recti, line_height: i32, baseline: i32) -> i32 {
if rect.height >= line_height {
return rect.y + (rect.height - line_height) / 2;
}
let baseline_center = rect.y + rect.height / 2;
let min_top = rect.y + rect.height - line_height;
let max_top = rect.y;
clamp_i32(baseline_center - baseline, min_top, max_top)
}
#[inline]
fn clamp_i32(x: i32, a: i32, b: i32) -> i32 {
min(b, max(a, x))
}
pub(crate) fn control_text_position_with_font(style: &Style, atlas: &AtlasHandle, font: FontId, text: &str, rect: Recti, opt: WidgetOption) -> Vec2i {
let tsize = atlas.get_text_size(font, text);
let padding = style.padding;
let line_height = atlas.get_font_height(font) as i32;
let baseline = atlas.get_font_baseline(font);
let y = baseline_aligned_top(rect, line_height, baseline);
let x = if opt.is_aligned_center() {
rect.x + (rect.width - tsize.width) / 2
} else if opt.is_aligned_right() {
rect.x + rect.width - tsize.width - padding
} else {
rect.x + padding
};
vec2(x, y)
}
impl<'a> DrawCtx<'a> {
pub(crate) fn new(
commands: &'a mut Vec<Command>,
triangle_vertices: &'a mut Vec<Vertex>,
clip_stack: &'a mut Vec<Recti>,
style: &'a Style,
atlas: &'a AtlasHandle,
) -> Self {
Self {
commands,
triangle_vertices,
clip_stack,
style,
atlas,
}
}
pub(crate) fn style(&self) -> &Style {
self.style
}
pub(crate) fn atlas(&self) -> &AtlasHandle {
self.atlas
}
pub(crate) fn current_clip_rect(&self) -> Recti {
self.clip_stack.last().copied().unwrap_or(UNCLIPPED_RECT)
}
pub(crate) fn clip_depth(&self) -> usize {
self.clip_stack.len()
}
pub(crate) fn push_clip_rect(&mut self, rect: Recti) {
let last = self.current_clip_rect();
self.clip_stack.push(intersect_clip_rect(last, rect));
}
pub(crate) fn pop_clip_rect(&mut self) {
self.clip_stack.pop();
}
pub(crate) fn replace_current_clip_rect(&mut self, rect: Recti) {
if let Some(top) = self.clip_stack.last_mut() {
*top = rect;
} else {
self.clip_stack.push(rect);
}
}
pub(crate) fn pop_clip_rect_to(&mut self, depth: usize) {
while self.clip_stack.len() > depth {
self.clip_stack.pop();
}
}
pub(crate) fn push_command(&mut self, cmd: Command) {
self.commands.push(cmd);
}
pub(crate) fn triangle_vertex_count(&self) -> usize {
self.triangle_vertices.len()
}
pub(crate) fn push_triangle_vertices(&mut self, v0: Vertex, v1: Vertex, v2: Vertex) {
self.triangle_vertices.push(v0);
self.triangle_vertices.push(v1);
self.triangle_vertices.push(v2);
}
pub(crate) fn set_clip(&mut self, rect: Recti) {
self.push_command(Command::Clip { rect });
}
pub(crate) fn emit_clipped<F>(&mut self, bounds: Recti, clip: Recti, emit: F)
where
F: FnOnce(&mut Self),
{
let clipped = clip_relation(bounds, clip);
if clipped == Clip::All {
return;
}
if clipped == Clip::Part {
self.set_clip(clip);
}
emit(self);
if clipped != Clip::None {
self.set_clip(UNCLIPPED_RECT);
}
}
pub(crate) fn check_clip(&self, r: Recti) -> Clip {
clip_relation(r, self.current_clip_rect())
}
pub(crate) fn draw_rect(&mut self, rect: Recti, color: Color) {
let rect = rect.intersect(&self.current_clip_rect()).unwrap_or_default();
if rect.width > 0 && rect.height > 0 {
self.push_command(Command::Recti { rect, color });
}
}
pub(crate) fn draw_box(&mut self, r: Recti, color: Color) {
self.draw_rect(rect(r.x + 1, r.y, r.width - 2, 1), color);
self.draw_rect(rect(r.x + 1, r.y + r.height - 1, r.width - 2, 1), color);
self.draw_rect(rect(r.x, r.y, 1, r.height), color);
self.draw_rect(rect(r.x + r.width - 1, r.y, 1, r.height), color);
}
pub(crate) fn draw_text(&mut self, font: FontId, text: &str, pos: Vec2i, color: Color) {
let size = self.atlas.get_text_size(font, text);
let bounds = rect(pos.x, pos.y, size.width, size.height);
let clip = self.current_clip_rect();
self.emit_clipped(bounds, clip, |draw| {
draw.push_command(Command::Text {
text: String::from(text),
pos,
color,
font,
});
});
}
pub(crate) fn draw_icon(&mut self, id: IconId, rect: Recti, color: Color) {
let clip = self.current_clip_rect();
self.emit_clipped(rect, clip, |draw| {
draw.push_command(Command::Icon { id, rect, color });
});
}
pub(crate) fn push_image(&mut self, image: Image, rect: Recti, color: Color) {
let clip = self.current_clip_rect();
self.emit_clipped(rect, clip, |draw| {
draw.push_command(Command::Image { image, rect, color });
});
}
pub(crate) fn draw_slot_with_function(&mut self, id: SlotId, rect: Recti, color: Color, f: Rc<dyn Fn(usize, usize) -> Color4b>) {
let clip = self.current_clip_rect();
self.emit_clipped(rect, clip, |draw| {
draw.push_command(Command::SlotRedraw { id, rect, color, payload: f });
});
}
pub(crate) fn draw_frame(&mut self, rect: Recti, colorid: ControlColor) {
let color = self.style.colors[colorid as usize];
self.draw_rect(rect, color);
if colorid == ControlColor::ScrollBase || colorid == ControlColor::ScrollThumb || colorid == ControlColor::TitleBG {
return;
}
let border_color = self.style.colors[ControlColor::Border as usize];
if border_color.a != 0 {
self.draw_box(expand_rect(rect, 1), border_color);
}
}
pub(crate) fn draw_widget_frame(&mut self, focused: bool, hovered: bool, rect: Recti, mut colorid: ControlColor, opt: WidgetOption) {
if opt.has_no_frame() {
return;
}
if focused {
colorid.focus()
} else if hovered {
colorid.hover()
}
self.draw_frame(rect, colorid);
}
pub(crate) fn draw_control_text_with_font(&mut self, font: FontId, text: &str, rect: Recti, colorid: ControlColor, opt: WidgetOption) {
let color = self.style.colors[colorid as usize];
let pos = control_text_position_with_font(self.style, self.atlas, font, text, rect, opt);
self.push_clip_rect(rect);
self.draw_text(font, text, pos, color);
self.pop_clip_rect();
}
}