use crate::color::Color;
use crate::device_scale::device_scale;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult};
use crate::geometry::{Rect, Size};
use crate::layout_props::{resolve_fit_or_stretch, HAnchor, Insets, VAnchor, WidgetBase};
use crate::widget::Widget;
fn place_cross_h(
anchor: HAnchor,
pad_l: f64,
inner_w: f64,
margin_l: f64,
margin_r: f64,
natural_w: f64,
min_w: f64,
max_w: f64,
) -> (f64, f64) {
let slot_w = (inner_w - margin_l - margin_r).max(0.0);
let actual_w = if anchor.is_stretch() {
slot_w.clamp(min_w, max_w)
} else if anchor == HAnchor::MAX_FIT_OR_STRETCH {
resolve_fit_or_stretch(natural_w, slot_w, true).clamp(min_w, max_w)
} else if anchor == HAnchor::MIN_FIT_OR_STRETCH {
resolve_fit_or_stretch(natural_w, slot_w, false).clamp(min_w, max_w)
} else {
natural_w.clamp(min_w, max_w)
};
let x = if anchor.contains(HAnchor::RIGHT) && !anchor.contains(HAnchor::LEFT) {
(pad_l + inner_w - margin_r - actual_w).max(pad_l)
} else if anchor.contains(HAnchor::CENTER) && !anchor.is_stretch() {
pad_l + margin_l + (slot_w - actual_w) * 0.5
} else {
pad_l + margin_l
};
(x, actual_w)
}
fn place_cross_v(
anchor: VAnchor,
pad_b: f64,
inner_h: f64,
margin_b: f64,
margin_t: f64,
natural_h: f64,
min_h: f64,
max_h: f64,
) -> (f64, f64) {
let slot_h = (inner_h - margin_b - margin_t).max(0.0);
let actual_h = if anchor.is_stretch() {
slot_h.clamp(min_h, max_h)
} else if anchor == VAnchor::MAX_FIT_OR_STRETCH {
resolve_fit_or_stretch(natural_h, slot_h, true).clamp(min_h, max_h)
} else if anchor == VAnchor::MIN_FIT_OR_STRETCH {
resolve_fit_or_stretch(natural_h, slot_h, false).clamp(min_h, max_h)
} else {
natural_h.clamp(min_h, max_h)
};
let y = if anchor.contains(VAnchor::TOP) && !anchor.contains(VAnchor::BOTTOM) {
(pad_b + inner_h - margin_t - actual_h).max(pad_b)
} else if anchor.contains(VAnchor::CENTER) && !anchor.is_stretch() {
pad_b + margin_b + (slot_h - actual_h) * 0.5
} else {
pad_b + margin_b
};
(y, actual_h)
}
pub struct FlexColumn {
bounds: Rect,
children: Vec<Box<dyn Widget>>,
flex_factors: Vec<f64>,
base: WidgetBase,
pub gap: f64,
pub inner_padding: Insets,
pub background: Color,
pub use_panel_bg: bool,
pub fit_width: bool,
pub top_anchor: bool,
}
impl FlexColumn {
pub fn new() -> Self {
Self {
bounds: Rect::default(),
children: Vec::new(),
flex_factors: Vec::new(),
base: WidgetBase::new(),
gap: 0.0,
inner_padding: Insets::ZERO,
background: Color::rgba(0.0, 0.0, 0.0, 0.0),
use_panel_bg: false,
fit_width: false,
top_anchor: false,
}
}
pub fn with_gap(mut self, gap: f64) -> Self {
self.gap = gap;
self
}
pub fn with_padding(mut self, p: f64) -> Self {
self.inner_padding = Insets::all(p);
self
}
pub fn with_inner_padding(mut self, p: Insets) -> Self {
self.inner_padding = p;
self
}
pub fn with_background(mut self, c: Color) -> Self {
self.background = c;
self
}
pub fn with_panel_bg(mut self) -> Self {
self.use_panel_bg = true;
self
}
pub fn with_fit_width(mut self, fit: bool) -> Self {
self.fit_width = fit;
self
}
pub fn with_top_anchor(mut self, on: bool) -> Self {
self.top_anchor = on;
self
}
pub fn with_margin(mut self, m: Insets) -> Self {
self.base.margin = m;
self
}
pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
self.base.h_anchor = h;
self
}
pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
self.base.v_anchor = v;
self
}
pub fn with_min_size(mut self, s: Size) -> Self {
self.base.min_size = s;
self
}
pub fn with_max_size(mut self, s: Size) -> Self {
self.base.max_size = s;
self
}
pub fn add(mut self, child: Box<dyn Widget>) -> Self {
self.children.push(child);
self.flex_factors.push(0.0);
self
}
pub fn add_flex(mut self, child: Box<dyn Widget>, flex: f64) -> Self {
self.children.push(child);
self.flex_factors.push(flex.max(0.0));
self
}
pub fn push(&mut self, child: Box<dyn Widget>, flex: f64) {
self.children.push(child);
self.flex_factors.push(flex.max(0.0));
}
}
impl Default for FlexColumn {
fn default() -> Self {
Self::new()
}
}
impl Widget for FlexColumn {
fn type_name(&self) -> &'static str {
"FlexColumn"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn margin(&self) -> Insets {
self.base.margin
}
fn widget_base(&self) -> Option<&WidgetBase> {
Some(&self.base)
}
fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
Some(&mut self.base)
}
fn padding(&self) -> Insets {
self.inner_padding
}
fn h_anchor(&self) -> HAnchor {
self.base.h_anchor
}
fn v_anchor(&self) -> VAnchor {
self.base.v_anchor
}
fn min_size(&self) -> Size {
self.base.min_size
}
fn max_size(&self) -> Size {
self.base.max_size
}
fn measure_min_height(&self, available_w: f64) -> f64 {
let pad_l = self.inner_padding.left;
let pad_r = self.inner_padding.right;
let pad_t = self.inner_padding.top;
let pad_b = self.inner_padding.bottom;
let inner_w = (available_w - pad_l - pad_r).max(0.0);
let scale = device_scale();
let n = self.children.len();
let mut total = 0.0_f64;
for child in self.children.iter() {
let m = child.margin().scale(scale);
let slot_w = (inner_w - m.left - m.right).max(0.0);
total += child.measure_min_height(slot_w) + m.vertical();
}
total += pad_t + pad_b;
if n > 1 {
total += self.gap * (n - 1) as f64;
}
total.max(self.base.min_size.height)
}
fn layout(&mut self, available: Size) -> Size {
let pad_l = self.inner_padding.left;
let pad_r = self.inner_padding.right;
let pad_t = self.inner_padding.top;
let pad_b = self.inner_padding.bottom;
let gap = self.gap;
let n = self.children.len();
if n == 0 {
return available;
}
let inner_w = (available.width - pad_l - pad_r).max(0.0);
let inner_h = (available.height - pad_t - pad_b).max(0.0);
let scale = device_scale();
let margins: Vec<Insets> = self
.children
.iter()
.map(|c| c.margin().scale(scale))
.collect();
let total_gap = if n > 1 { gap * (n - 1) as f64 } else { 0.0 };
let mut content_heights = vec![0.0f64; n];
let mut total_fixed_with_margins = 0.0f64;
let mut total_flex = 0.0f64;
let mut total_flex_margin_v = 0.0f64;
let mut max_child_natural_w = 0.0f64;
for i in 0..n {
let m = &margins[i];
let slot_w = (inner_w - m.left - m.right).max(0.0);
if self.flex_factors[i] == 0.0 {
let desired = self.children[i].layout(Size::new(slot_w, inner_h));
let clamped_h = desired.height.clamp(
self.children[i].min_size().height,
self.children[i].max_size().height,
);
content_heights[i] = clamped_h;
total_fixed_with_margins += clamped_h + m.vertical();
max_child_natural_w = max_child_natural_w.max(desired.width + m.horizontal());
} else {
total_flex += self.flex_factors[i];
total_flex_margin_v += m.vertical();
}
}
let remaining =
(inner_h - total_fixed_with_margins - total_gap - total_flex_margin_v).max(0.0);
let flex_unit = if total_flex > 0.0 {
remaining / total_flex
} else {
0.0
};
for i in 0..n {
if self.flex_factors[i] > 0.0 {
let raw = self.flex_factors[i] * flex_unit;
content_heights[i] = raw.clamp(
self.children[i].min_size().height,
self.children[i].max_size().height,
);
}
}
let natural_content_h = total_fixed_with_margins + total_gap;
let effective_h = if total_flex > 0.0 {
inner_h
} else {
natural_content_h
};
let measuring_natural_height = available.height > 1.0e9;
let mut cursor_y = if self.top_anchor || !measuring_natural_height {
available.height - pad_t
} else {
pad_b + effective_h
};
for i in 0..n {
let m = &margins[i];
let slot_w = (inner_w - m.left - m.right).max(0.0);
let content_h = content_heights[i];
cursor_y -= m.top;
let child_bottom = cursor_y - content_h;
let desired = self.children[i].layout(Size::new(slot_w, content_h));
let natural_w = desired.width;
let h_anchor = self.children[i].h_anchor();
let min_w = self.children[i].min_size().width;
let max_w = self.children[i].max_size().width;
let (child_x, child_w) = place_cross_h(
h_anchor, pad_l, inner_w, m.left, m.right, natural_w, min_w, max_w,
);
self.children[i].set_bounds(Rect::new(
child_x.round(),
child_bottom.round(),
child_w.round(),
content_h.round(),
));
cursor_y = child_bottom - m.bottom - gap;
}
let reported_w = if self.fit_width {
max_child_natural_w + pad_l + pad_r
} else {
available.width
};
if total_flex > 0.0 {
Size::new(reported_w, available.height)
} else {
Size::new(reported_w, natural_content_h + pad_t + pad_b)
}
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
let bg = if self.use_panel_bg {
Some(ctx.visuals().panel_fill)
} else if self.background.a > 0.001 {
Some(self.background)
} else {
None
};
if let Some(color) = bg {
let w = self.bounds.width;
let h = self.bounds.height;
ctx.set_fill_color(color);
ctx.begin_path();
ctx.rect(0.0, 0.0, w, h);
ctx.fill();
}
}
fn on_event(&mut self, _: &Event) -> EventResult {
EventResult::Ignored
}
}
pub struct FlexRow {
bounds: Rect,
children: Vec<Box<dyn Widget>>,
flex_factors: Vec<f64>,
base: WidgetBase,
pub gap: f64,
pub inner_padding: Insets,
pub background: Color,
}
impl FlexRow {
pub fn new() -> Self {
Self {
bounds: Rect::default(),
children: Vec::new(),
flex_factors: Vec::new(),
base: WidgetBase::new(),
gap: 0.0,
inner_padding: Insets::ZERO,
background: Color::rgba(0.0, 0.0, 0.0, 0.0),
}
}
pub fn with_gap(mut self, gap: f64) -> Self {
self.gap = gap;
self
}
pub fn with_padding(mut self, p: f64) -> Self {
self.inner_padding = Insets::all(p);
self
}
pub fn with_inner_padding(mut self, p: Insets) -> Self {
self.inner_padding = p;
self
}
pub fn with_background(mut self, c: Color) -> Self {
self.background = c;
self
}
pub fn with_margin(mut self, m: Insets) -> Self {
self.base.margin = m;
self
}
pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
self.base.h_anchor = h;
self
}
pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
self.base.v_anchor = v;
self
}
pub fn with_min_size(mut self, s: Size) -> Self {
self.base.min_size = s;
self
}
pub fn with_max_size(mut self, s: Size) -> Self {
self.base.max_size = s;
self
}
pub fn add(mut self, child: Box<dyn Widget>) -> Self {
self.children.push(child);
self.flex_factors.push(0.0);
self
}
pub fn add_flex(mut self, child: Box<dyn Widget>, flex: f64) -> Self {
self.children.push(child);
self.flex_factors.push(flex.max(0.0));
self
}
pub fn push(&mut self, child: Box<dyn Widget>, flex: f64) {
self.children.push(child);
self.flex_factors.push(flex.max(0.0));
}
}
impl Default for FlexRow {
fn default() -> Self {
Self::new()
}
}
impl Widget for FlexRow {
fn type_name(&self) -> &'static str {
"FlexRow"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn margin(&self) -> Insets {
self.base.margin
}
fn widget_base(&self) -> Option<&WidgetBase> {
Some(&self.base)
}
fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
Some(&mut self.base)
}
fn padding(&self) -> Insets {
self.inner_padding
}
fn h_anchor(&self) -> HAnchor {
self.base.h_anchor
}
fn v_anchor(&self) -> VAnchor {
self.base.v_anchor
}
fn min_size(&self) -> Size {
self.base.min_size
}
fn max_size(&self) -> Size {
self.base.max_size
}
fn layout(&mut self, available: Size) -> Size {
let pad_l = self.inner_padding.left;
let pad_r = self.inner_padding.right;
let pad_t = self.inner_padding.top;
let pad_b = self.inner_padding.bottom;
let gap = self.gap;
let n = self.children.len();
if n == 0 {
return available;
}
let inner_w = (available.width - pad_l - pad_r).max(0.0);
let inner_h = (available.height - pad_t - pad_b).max(0.0);
let scale = device_scale();
let margins: Vec<Insets> = self
.children
.iter()
.map(|c| c.margin().scale(scale))
.collect();
let total_gap = if n > 1 { gap * (n - 1) as f64 } else { 0.0 };
let mut content_widths = vec![0.0f64; n];
let mut total_fixed_with_margins = 0.0f64;
let mut total_flex = 0.0f64;
let mut total_flex_margin_h = 0.0f64;
for i in 0..n {
let m = &margins[i];
let slot_h = (inner_h - m.bottom - m.top).max(0.0);
if self.flex_factors[i] == 0.0 {
let desired = self.children[i].layout(Size::new(inner_w, slot_h));
let clamped_w = desired.width.clamp(
self.children[i].min_size().width,
self.children[i].max_size().width,
);
content_widths[i] = clamped_w;
total_fixed_with_margins += clamped_w + m.horizontal();
} else {
total_flex += self.flex_factors[i];
total_flex_margin_h += m.horizontal();
}
}
let remaining =
(inner_w - total_fixed_with_margins - total_gap - total_flex_margin_h).max(0.0);
let flex_unit = if total_flex > 0.0 {
remaining / total_flex
} else {
0.0
};
for i in 0..n {
if self.flex_factors[i] > 0.0 {
let raw = self.flex_factors[i] * flex_unit;
content_widths[i] = raw.clamp(
self.children[i].min_size().width,
self.children[i].max_size().width,
);
}
}
let mut cursor_x = pad_l;
let mut max_slot_h = 0.0f64;
for i in 0..n {
let m = &margins[i];
let slot_h = (inner_h - m.bottom - m.top).max(0.0);
let content_w = content_widths[i];
cursor_x += m.left;
let desired = self.children[i].layout(Size::new(content_w, slot_h));
let natural_h = desired.height;
let v_anchor = self.children[i].v_anchor();
let min_h = self.children[i].min_size().height;
let max_h = self.children[i].max_size().height;
let (child_y, child_h) = place_cross_v(
v_anchor, pad_b, inner_h, m.bottom, m.top, natural_h, min_h, max_h,
);
self.children[i].set_bounds(Rect::new(
cursor_x.round(),
child_y.round(),
content_w.round(),
child_h.round(),
));
max_slot_h = max_slot_h.max(child_h + m.vertical());
cursor_x += content_w + m.right + gap;
}
let natural_h = max_slot_h + pad_t + pad_b;
Size::new(available.width, natural_h)
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
if self.background.a > 0.001 {
let w = self.bounds.width;
let h = self.bounds.height;
ctx.set_fill_color(self.background);
ctx.begin_path();
ctx.rect(0.0, 0.0, w, h);
ctx.fill();
}
}
fn on_event(&mut self, _: &Event) -> EventResult {
EventResult::Ignored
}
}