use std::ops::{Index, IndexMut};
use num_rational::Ratio;
use crate::console::{Console, ConsoleOptions};
use crate::measure::{Measurement, RichMeasure};
use crate::renderables::Renderable;
use crate::segment::{Segment, adjust_line_length, split_lines};
use crate::text::{JustifyMethod, OverflowMethod, Text};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Region {
pub x: usize,
pub y: usize,
pub width: usize,
pub height: usize,
}
impl Region {
#[must_use]
pub const fn new(x: usize, y: usize, width: usize, height: usize) -> Self {
Self {
x,
y,
width,
height,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayoutSplitter {
Row,
Column,
}
impl LayoutSplitter {
fn divide<'a>(self, children: &'a [&Layout], region: Region) -> Vec<(Region, &'a Layout)> {
match self {
Self::Row => divide_row(children, region),
Self::Column => divide_column(children, region),
}
}
}
pub struct Layout {
renderable: Option<Box<dyn Renderable + Send + Sync>>,
name: Option<String>,
size: Option<usize>,
minimum_size: usize,
ratio: usize,
visible: bool,
splitter: LayoutSplitter,
children: Vec<Layout>,
}
impl Default for Layout {
fn default() -> Self {
Self {
renderable: None,
name: None,
size: None,
minimum_size: 1,
ratio: 1,
visible: true,
splitter: LayoutSplitter::Column,
children: Vec::new(),
}
}
}
impl Layout {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_renderable<R>(renderable: R) -> Self
where
R: Renderable + Send + Sync + 'static,
{
Self {
renderable: Some(Box::new(renderable)),
..Self::default()
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn size(mut self, size: usize) -> Self {
self.size = Some(size);
self
}
#[must_use]
pub fn minimum_size(mut self, minimum_size: usize) -> Self {
self.minimum_size = minimum_size.max(1);
self
}
#[must_use]
pub fn ratio(mut self, ratio: usize) -> Self {
self.ratio = ratio.max(1);
self
}
#[must_use]
pub fn visible(mut self, visible: bool) -> Self {
self.visible = visible;
self
}
#[must_use]
pub fn renderable<R>(mut self, renderable: R) -> Self
where
R: Renderable + Send + Sync + 'static,
{
self.renderable = Some(Box::new(renderable));
self
}
pub fn update<R>(&mut self, renderable: R)
where
R: Renderable + Send + Sync + 'static,
{
self.renderable = Some(Box::new(renderable));
}
pub fn split(&mut self, layouts: Vec<Layout>, splitter: LayoutSplitter) {
self.splitter = splitter;
self.children = layouts;
}
pub fn split_row(&mut self, layouts: Vec<Layout>) {
self.split(layouts, LayoutSplitter::Row);
}
pub fn split_column(&mut self, layouts: Vec<Layout>) {
self.split(layouts, LayoutSplitter::Column);
}
pub fn add_split(&mut self, layouts: Vec<Layout>) {
self.children.extend(layouts);
}
pub fn unsplit(&mut self) {
self.children.clear();
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&Layout> {
if self.name.as_deref() == Some(name) {
return Some(self);
}
for child in &self.children {
if let Some(found) = child.get(name) {
return Some(found);
}
}
None
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut Layout> {
if self.name.as_deref() == Some(name) {
return Some(self);
}
for child in &mut self.children {
if let Some(found) = child.get_mut(name) {
return Some(found);
}
}
None
}
fn visible_children(&self) -> Vec<&Layout> {
self.children.iter().filter(|c| c.visible).collect()
}
fn render_region(
&self,
console: &Console,
options: &ConsoleOptions,
region: Region,
) -> Vec<Vec<Segment<'static>>> {
if !self.visible {
return blank_lines(region.width, region.height);
}
let child_layouts = self.visible_children();
if child_layouts.is_empty() {
return self.render_leaf(console, options, region);
}
let mut rendered_children = Vec::new();
for (child_region, child) in self.splitter.divide(&child_layouts, region) {
let child_options = options.update_dimensions(child_region.width, child_region.height);
let child_lines = child.render_region(console, &child_options, child_region);
rendered_children.push((child_region, child_lines));
}
merge_children(region, &rendered_children)
}
fn render_leaf(
&self,
console: &Console,
options: &ConsoleOptions,
region: Region,
) -> Vec<Vec<Segment<'static>>> {
let content_segments = if let Some(renderable) = self.renderable.as_ref() {
renderable
.render(
console,
&options.update_dimensions(region.width, region.height),
)
.into_iter()
.map(Segment::into_owned)
.collect()
} else {
placeholder_segments(self.name.as_deref(), region.width, region.height)
};
let mut lines = split_lines(content_segments.into_iter());
let mut padded = Vec::new();
for mut line in lines.drain(..) {
line = adjust_line_length(line, region.width, None, true);
padded.push(line);
}
if padded.len() > region.height {
padded.truncate(region.height);
} else if padded.len() < region.height {
let filler = vec![Segment::new(" ".repeat(region.width), None)];
for _ in padded.len()..region.height {
padded.push(filler.clone());
}
}
padded
}
}
impl Renderable for Layout {
fn render<'a>(&'a self, console: &Console, options: &ConsoleOptions) -> Vec<Segment<'a>> {
let width = options.max_width;
let height = options.height.unwrap_or(options.size.height);
let region = Region::new(0, 0, width, height);
let lines = self.render_region(console, options, region);
let mut segments: Vec<Segment<'static>> = Vec::new();
for (idx, mut line) in lines.into_iter().enumerate() {
segments.append(&mut line);
if idx + 1 < height {
segments.push(Segment::line());
}
}
segments.into_iter().collect()
}
}
impl RichMeasure for Layout {
fn rich_measure(&self, _console: &Console, options: &ConsoleOptions) -> Measurement {
Measurement::exact(options.max_width)
}
}
impl Index<&str> for Layout {
type Output = Layout;
fn index(&self, name: &str) -> &Self::Output {
self.get(name).unwrap_or_else(|| {
panic!("Layout not found: {name}");
})
}
}
impl IndexMut<&str> for Layout {
fn index_mut(&mut self, name: &str) -> &mut Self::Output {
self.get_mut(name).unwrap_or_else(|| {
panic!("Layout not found: {name}");
})
}
}
fn blank_lines(width: usize, height: usize) -> Vec<Vec<Segment<'static>>> {
let filler = vec![Segment::new(" ".repeat(width), None)];
(0..height).map(|_| filler.clone()).collect()
}
fn placeholder_segments(name: Option<&str>, width: usize, height: usize) -> Vec<Segment<'static>> {
let label = if let Some(name) = name {
format!("{name} ({width} x {height})")
} else {
format!("({width} x {height})")
};
let mut text = Text::new(label);
text.overflow = OverflowMethod::Ellipsis;
text.justify = JustifyMethod::Center;
text.pad(width, JustifyMethod::Center);
text.render("")
.into_iter()
.map(Segment::into_owned)
.collect()
}
fn merge_children(
region: Region,
children: &[(Region, Vec<Vec<Segment<'static>>>)],
) -> Vec<Vec<Segment<'static>>> {
let mut lines: Vec<Vec<Segment<'static>>> = Vec::with_capacity(region.height);
for row in 0..region.height {
let mut line = Vec::new();
let mut cursor = 0usize;
let abs_row = region.y + row;
let mut children_for_row: Vec<(usize, &Vec<Segment<'static>>)> = Vec::new();
for (child_region, child_lines) in children {
if abs_row >= child_region.y && abs_row < child_region.y + child_region.height {
let child_line = &child_lines[abs_row - child_region.y];
children_for_row.push((child_region.x - region.x, child_line));
}
}
children_for_row.sort_by_key(|(x, _)| *x);
for (x, child_line) in children_for_row {
if x > cursor {
line.push(Segment::new(" ".repeat(x - cursor), None));
cursor = x;
}
line.extend(child_line.clone());
cursor =
cursor.saturating_add(child_line.iter().map(Segment::cell_length).sum::<usize>());
}
if cursor < region.width {
line.push(Segment::new(" ".repeat(region.width - cursor), None));
}
lines.push(adjust_line_length(line, region.width, None, true));
}
lines
}
fn divide_row<'a>(children: &'a [&Layout], region: Region) -> Vec<(Region, &'a Layout)> {
let widths = ratio_resolve(region.width, children);
let mut result = Vec::new();
let mut offset = 0;
for (child, width) in children.iter().zip(widths) {
let child_region = Region::new(region.x + offset, region.y, width, region.height);
result.push((child_region, *child));
offset += width;
}
result
}
fn divide_column<'a>(children: &'a [&Layout], region: Region) -> Vec<(Region, &'a Layout)> {
let heights = ratio_resolve(region.height, children);
let mut result = Vec::new();
let mut offset = 0;
for (child, height) in children.iter().zip(heights) {
let child_region = Region::new(region.x, region.y + offset, region.width, height);
result.push((child_region, *child));
offset += height;
}
result
}
fn ratio_resolve(total: usize, children: &[&Layout]) -> Vec<usize> {
if children.is_empty() {
return Vec::new();
}
let mut sizes = Vec::with_capacity(children.len());
let mut mins = Vec::with_capacity(children.len());
let mut ratios = Vec::with_capacity(children.len());
let mut fixed_total = 0usize;
let mut flex_min_total = 0usize;
for child in children {
let min_size = child.minimum_size.max(1);
mins.push(min_size);
if let Some(size) = child.size {
let size = size.max(min_size);
sizes.push(size);
ratios.push(0);
fixed_total += size;
} else {
sizes.push(min_size);
ratios.push(child.ratio.max(1));
flex_min_total += min_size;
}
}
let mut remaining = total.saturating_sub(fixed_total + flex_min_total);
let total_ratio: usize = ratios.iter().sum();
if total_ratio > 0 && remaining > 0 {
let mut distributed = 0usize;
let mut flex_idx = 0usize;
let flex_count = ratios.iter().filter(|&&r| r > 0).count();
for (i, &ratio) in ratios.iter().enumerate() {
if ratio > 0 {
flex_idx += 1;
let share = Ratio::new(ratio, total_ratio);
let extra = if flex_idx == flex_count {
remaining - distributed
} else {
(share * remaining).round().to_integer()
};
sizes[i] = sizes[i].saturating_add(extra);
distributed += extra;
}
}
remaining = remaining.saturating_sub(distributed);
}
if remaining > 0 {
let mut i = 0usize;
while remaining > 0 && !sizes.is_empty() {
sizes[i] = sizes[i].saturating_add(1);
remaining = remaining.saturating_sub(1);
i = (i + 1) % sizes.len();
}
}
clamp_sizes(sizes, &mins, total)
}
fn clamp_sizes(mut sizes: Vec<usize>, mins: &[usize], total: usize) -> Vec<usize> {
let mut sum: usize = sizes.iter().sum();
if sum <= total {
return sizes;
}
while sum > total {
let mut reduced = false;
for idx in (0..sizes.len()).rev() {
if sizes[idx] > mins[idx] {
sizes[idx] -= 1;
sum -= 1;
reduced = true;
if sum == total {
break;
}
}
}
if !reduced {
break;
}
}
if sum > total {
let mut idx = 0usize;
while sum > total {
if sizes[idx] > 0 {
sizes[idx] -= 1;
sum -= 1;
}
idx = (idx + 1) % sizes.len();
}
}
sizes
}
#[cfg(test)]
mod tests {
use super::*;
use crate::renderables::Panel;
#[test]
fn test_region_new() {
let region = Region::new(5, 10, 80, 24);
assert_eq!(region.x, 5);
assert_eq!(region.y, 10);
assert_eq!(region.width, 80);
assert_eq!(region.height, 24);
}
#[test]
fn test_region_equality() {
let r1 = Region::new(0, 0, 10, 10);
let r2 = Region::new(0, 0, 10, 10);
let r3 = Region::new(1, 0, 10, 10);
assert_eq!(r1, r2);
assert_ne!(r1, r3);
}
#[test]
fn test_region_clone() {
let r1 = Region::new(1, 2, 3, 4);
let r2 = r1; assert_eq!(r1, r2);
}
#[test]
fn test_layout_splitter_variants() {
let row = LayoutSplitter::Row;
let col = LayoutSplitter::Column;
assert_ne!(row, col);
assert_eq!(row, LayoutSplitter::Row);
assert_eq!(col, LayoutSplitter::Column);
}
#[test]
fn test_layout_new() {
let layout = Layout::new();
assert!(layout.renderable.is_none());
assert!(layout.name.is_none());
assert!(layout.size.is_none());
assert_eq!(layout.minimum_size, 1);
assert_eq!(layout.ratio, 1);
assert!(layout.visible);
assert!(layout.children.is_empty());
}
#[test]
fn test_layout_default() {
let layout = Layout::default();
assert!(layout.renderable.is_none());
assert_eq!(layout.splitter, LayoutSplitter::Column);
}
#[test]
fn test_layout_from_renderable() {
let text = Text::new("Hello");
let layout = Layout::from_renderable(text);
assert!(layout.renderable.is_some());
}
#[test]
fn test_layout_builder_chain() {
let layout = Layout::new()
.name("main")
.size(50)
.minimum_size(10)
.ratio(2)
.visible(true);
assert_eq!(layout.name, Some("main".to_string()));
assert_eq!(layout.size, Some(50));
assert_eq!(layout.minimum_size, 10);
assert_eq!(layout.ratio, 2);
assert!(layout.visible);
}
#[test]
fn test_layout_minimum_size_clamped() {
let layout = Layout::new().minimum_size(0);
assert_eq!(layout.minimum_size, 1);
}
#[test]
fn test_layout_ratio_clamped() {
let layout = Layout::new().ratio(0);
assert_eq!(layout.ratio, 1);
}
#[test]
fn test_layout_split_row_dimensions() {
let mut layout = Layout::new();
layout.split_row(vec![
Layout::new().name("left"),
Layout::new().name("right"),
]);
let console = Console::builder().width(10).height(2).build();
let options = console.options();
let segments = layout.render(&console, &options);
let lines = split_lines(segments.into_iter().map(Segment::into_owned));
assert_eq!(lines.len(), 2);
for line in &lines {
let width: usize = line.iter().map(Segment::cell_length).sum();
assert_eq!(width, 10);
}
}
#[test]
fn test_layout_split_column() {
let mut layout = Layout::new();
layout.split_column(vec![
Layout::new().name("top"),
Layout::new().name("bottom"),
]);
assert_eq!(layout.splitter, LayoutSplitter::Column);
assert_eq!(layout.children.len(), 2);
}
#[test]
fn test_layout_split_row() {
let mut layout = Layout::new();
layout.split_row(vec![
Layout::new().name("left"),
Layout::new().name("right"),
]);
assert_eq!(layout.splitter, LayoutSplitter::Row);
assert_eq!(layout.children.len(), 2);
}
#[test]
fn test_layout_add_split() {
let mut layout = Layout::new();
layout.split_column(vec![Layout::new().name("first")]);
layout.add_split(vec![Layout::new().name("second")]);
assert_eq!(layout.children.len(), 2);
}
#[test]
fn test_layout_unsplit() {
let mut layout = Layout::new();
layout.split_column(vec![Layout::new().name("a"), Layout::new().name("b")]);
layout.unsplit();
assert!(layout.children.is_empty());
}
#[test]
fn test_layout_named_lookup() {
let mut layout = Layout::new().name("root");
layout.split_column(vec![
Layout::new().name("header").size(1),
Layout::new().name("body").ratio(2),
]);
assert!(layout.get("root").is_some());
assert!(layout.get("header").is_some());
assert!(layout.get("body").is_some());
assert!(layout.get("missing").is_none());
}
#[test]
fn test_layout_get_mut() {
let mut layout = Layout::new().name("root");
layout.split_column(vec![Layout::new().name("child")]);
let child = layout.get_mut("child");
assert!(child.is_some());
if let Some(child) = layout.get_mut("child") {
child.update(Text::new("Updated"));
}
}
#[test]
fn test_layout_nested_lookup() {
let mut layout = Layout::new().name("root");
let mut middle = Layout::new().name("middle");
middle.split_column(vec![Layout::new().name("deep")]);
layout.split_column(vec![middle]);
assert!(layout.get("root").is_some());
assert!(layout.get("middle").is_some());
assert!(layout.get("deep").is_some());
}
#[test]
fn test_layout_index_trait() {
let mut layout = Layout::new().name("root");
layout.split_column(vec![Layout::new().name("child")]);
let child = &layout["child"];
assert_eq!(child.name, Some("child".to_string()));
}
#[test]
fn test_layout_index_mut_trait() {
let mut layout = Layout::new().name("root");
layout.split_column(vec![Layout::new().name("child")]);
layout["child"].update(Text::new("New content"));
}
#[test]
#[should_panic(expected = "Layout not found")]
fn test_layout_index_missing_panics() {
let layout = Layout::new().name("root");
let _ = &layout["nonexistent"];
}
#[test]
fn test_layout_invisible() {
let mut layout = Layout::new();
layout.split_row(vec![
Layout::new().name("visible"),
Layout::new().name("hidden").visible(false),
]);
let visible = layout.visible_children();
assert_eq!(visible.len(), 1);
}
#[test]
fn test_layout_all_invisible() {
let mut layout = Layout::new();
layout.split_row(vec![
Layout::new().name("a").visible(false),
Layout::new().name("b").visible(false),
]);
let visible = layout.visible_children();
assert!(visible.is_empty());
}
#[test]
fn test_layout_update() {
let mut layout = Layout::new();
assert!(layout.renderable.is_none());
layout.update(Text::new("Content"));
assert!(layout.renderable.is_some());
}
#[test]
fn test_layout_renderable_builder() {
let layout = Layout::new().renderable(Text::new("Builder content"));
assert!(layout.renderable.is_some());
}
#[test]
fn test_ratio_resolve_equal() {
let children: Vec<Layout> = vec![Layout::new().ratio(1), Layout::new().ratio(1)];
let refs: Vec<&Layout> = children.iter().collect();
let sizes = ratio_resolve(10, &refs);
assert_eq!(sizes.len(), 2);
assert_eq!(sizes[0], 5);
assert_eq!(sizes[1], 5);
}
#[test]
fn test_ratio_resolve_unequal() {
let children: Vec<Layout> = vec![Layout::new().ratio(1), Layout::new().ratio(2)];
let refs: Vec<&Layout> = children.iter().collect();
let sizes = ratio_resolve(9, &refs);
assert_eq!(sizes.len(), 2);
assert_eq!(sizes[0], 3);
assert_eq!(sizes[1], 6);
}
#[test]
fn test_ratio_resolve_with_fixed_size() {
let children: Vec<Layout> = vec![
Layout::new().size(5), Layout::new().ratio(1), ];
let refs: Vec<&Layout> = children.iter().collect();
let sizes = ratio_resolve(10, &refs);
assert_eq!(sizes.len(), 2);
assert_eq!(sizes[0], 5); assert_eq!(sizes[1], 5); }
#[test]
fn test_ratio_resolve_empty() {
let children: Vec<Layout> = vec![];
let refs: Vec<&Layout> = children.iter().collect();
let sizes = ratio_resolve(10, &refs);
assert!(sizes.is_empty());
}
#[test]
fn test_ratio_resolve_minimum_size() {
let children: Vec<Layout> = vec![
Layout::new().minimum_size(8), Layout::new().minimum_size(8), ];
let refs: Vec<&Layout> = children.iter().collect();
let sizes = ratio_resolve(10, &refs);
assert_eq!(sizes.len(), 2);
assert_eq!(sizes.iter().sum::<usize>(), 10);
}
#[test]
fn test_layout_render_empty() {
let layout = Layout::new().name("empty");
let console = Console::builder()
.width(20)
.height(5)
.force_terminal(true)
.build();
let options = console.options();
let segments = layout.render(&console, &options);
let text: String = segments.iter().map(|s| s.text.as_ref()).collect();
assert!(text.contains("empty") || text.contains("20") || text.contains('5'));
}
#[test]
fn test_layout_render_with_content() {
let layout = Layout::new().renderable(Text::new("Hello"));
let console = Console::builder()
.width(20)
.height(3)
.force_terminal(true)
.build();
let options = console.options();
let segments = layout.render(&console, &options);
let text: String = segments.iter().map(|s| s.text.as_ref()).collect();
assert!(text.contains("Hello"));
}
#[test]
fn test_layout_render_nested() {
let mut layout = Layout::new();
layout.split_column(vec![
Layout::new().name("top").renderable(Text::new("TOP")),
Layout::new().name("bottom").renderable(Text::new("BOTTOM")),
]);
let console = Console::builder()
.width(20)
.height(4)
.force_terminal(true)
.build();
let options = console.options();
let segments = layout.render(&console, &options);
let text: String = segments.iter().map(|s| s.text.as_ref()).collect();
assert!(text.contains("TOP") || text.contains("BOTTOM"));
}
#[test]
fn test_layout_render_row_split() {
let mut layout = Layout::new();
layout.split_row(vec![
Layout::new().name("left").renderable(Text::new("L")),
Layout::new().name("right").renderable(Text::new("R")),
]);
let console = Console::builder()
.width(20)
.height(2)
.force_terminal(true)
.build();
let options = console.options();
let segments = layout.render(&console, &options);
let lines = split_lines(segments.into_iter().map(Segment::into_owned));
for line in lines {
let width: usize = line.iter().map(Segment::cell_length).sum();
assert_eq!(width, 20);
}
}
#[test]
fn test_layout_single_child() {
let mut layout = Layout::new();
layout.split_column(vec![Layout::new().name("only")]);
let console = Console::builder()
.width(10)
.height(5)
.force_terminal(true)
.build();
let options = console.options();
let segments = layout.render(&console, &options);
let lines = split_lines(segments.into_iter().map(Segment::into_owned));
assert_eq!(lines.len(), 5);
}
#[test]
fn test_layout_measure() {
let layout = Layout::new();
let console = Console::builder().width(80).height(24).build();
let options = console.options();
let measurement = layout.rich_measure(&console, &options);
assert_eq!(measurement.minimum, 80);
assert_eq!(measurement.maximum, 80);
}
#[test]
fn test_blank_lines() {
let lines = blank_lines(5, 3);
assert_eq!(lines.len(), 3);
for line in lines {
let width: usize = line.iter().map(Segment::cell_length).sum();
assert_eq!(width, 5);
}
}
#[test]
fn test_placeholder_segments_with_name() {
let segments = placeholder_segments(Some("myname"), 20, 5);
let text: String = segments.iter().map(|s| s.text.as_ref()).collect();
assert!(text.contains("myname"));
assert!(text.contains("20"));
assert!(text.contains('5'));
}
#[test]
fn test_placeholder_segments_without_name() {
let segments = placeholder_segments(None, 20, 5);
let text: String = segments.iter().map(|s| s.text.as_ref()).collect();
assert!(text.contains("20"));
assert!(text.contains('5'));
}
#[test]
fn test_layout_with_panel() {
let layout = Layout::new().renderable(Panel::from_text("Panel content").title("Test"));
let console = Console::builder()
.width(30)
.height(5)
.force_terminal(true)
.build();
let options = console.options();
let segments = layout.render(&console, &options);
let text: String = segments.iter().map(|s| s.text.as_ref()).collect();
assert!(text.contains("Panel content") || text.contains("Test"));
}
#[test]
fn test_divide_row() {
let children: Vec<Layout> = vec![Layout::new().ratio(1), Layout::new().ratio(1)];
let refs: Vec<&Layout> = children.iter().collect();
let region = Region::new(0, 0, 10, 5);
let result = divide_row(&refs, region);
assert_eq!(result.len(), 2);
assert_eq!(result[0].0.x, 0);
assert_eq!(result[0].0.width, 5);
assert_eq!(result[1].0.x, 5);
assert_eq!(result[1].0.width, 5);
assert_eq!(result[0].0.height, 5);
assert_eq!(result[1].0.height, 5);
}
#[test]
fn test_divide_column() {
let children: Vec<Layout> = vec![Layout::new().ratio(1), Layout::new().ratio(1)];
let refs: Vec<&Layout> = children.iter().collect();
let region = Region::new(0, 0, 10, 6);
let result = divide_column(&refs, region);
assert_eq!(result.len(), 2);
assert_eq!(result[0].0.y, 0);
assert_eq!(result[0].0.height, 3);
assert_eq!(result[1].0.y, 3);
assert_eq!(result[1].0.height, 3);
assert_eq!(result[0].0.width, 10);
assert_eq!(result[1].0.width, 10);
}
}