[bootsier] Añade más componentes y repasa código

- Se incorpora nuevo componente Dropdown.
- Se crea un componente Navbar con soporte para marca, elementos de
navegación.
- Se implementa el componente Offcanvas con opciones de posición,
visibilidad y fondo personalizables.
- Mejora el manejo de imágenes con un nuevo componente de Image.
- Se reorganizan los componentes del tema para una mejor estructura y
usabilidad.
This commit is contained in:
Manuel Cillero 2025-10-19 21:57:15 +02:00
parent cf40af13bb
commit 82837c622e
29 changed files with 2608 additions and 9 deletions

View file

@ -0,0 +1,202 @@
use pagetop::prelude::*;
use crate::prelude::*;
use std::fmt;
// **< BorderSize >*********************************************************************************
/// Tamaño (**ancho**) para los bordes ([`Border`]).
///
/// Mapea a `border`, `border-0` y `border-{1..5}`:
///
/// - `None` no añade clase (devuelve `""`).
/// - `Default` genera `border` (borde por defecto del tema).
/// - `Zero` genera `border-0` (sin borde).
/// - `Scale{1..5}` genera `border-{1..5}` (ancho creciente).
#[derive(AutoDefault)]
pub enum BorderSize {
#[default]
None,
Default,
Zero,
Scale1,
Scale2,
Scale3,
Scale4,
Scale5,
}
impl BorderSize {
#[rustfmt::skip]
fn to_class(&self, prefix: impl AsRef<str>) -> String {
match self {
Self::None => String::new(),
Self::Default => String::from(prefix.as_ref()),
Self::Zero => join!(prefix, "-0"),
Self::Scale1 => join!(prefix, "-1"),
Self::Scale2 => join!(prefix, "-2"),
Self::Scale3 => join!(prefix, "-3"),
Self::Scale4 => join!(prefix, "-4"),
Self::Scale5 => join!(prefix, "-5"),
}
}
}
impl fmt::Display for BorderSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_class("border"))
}
}
// **< Border >*************************************************************************************
/// Agrupa propiedades para crear **bordes**.
///
/// Permite:
///
/// - Definir un tamaño **global** para todo el borde (`size`).
/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**,
/// respetando LTR/RTL).
/// - Aplicar un **color** al borde (`BorderColor`).
/// - Aplicar un nivel de **opacidad** (`BorderOpacity`).
///
/// # Comportamiento aditivo / sustractivo
///
/// - **Aditivo**: basta con crear un borde sin tamaño con `Border::new()` para ir añadiendo cada
/// lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`.
///
/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando
/// `Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`.
///
/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado.
///
/// # Ejemplos
///
/// **Borde global:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let b = Border::with(BorderSize::Scale2);
/// assert_eq!(b.to_string(), "border-2");
/// ```
///
/// **Aditivo (solo borde superior):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let b = Border::new().with_top(BorderSize::Scale1);
/// assert_eq!(b.to_string(), "border-top-1");
/// ```
///
/// **Sustractivo (borde global menos el superior):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let b = Border::with(BorderSize::Default).with_top(BorderSize::Zero);
/// assert_eq!(b.to_string(), "border border-top-0");
/// ```
///
/// **Ancho por lado (lado lógico inicial a 2 y final a 4):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let b = Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4);
/// assert_eq!(b.to_string(), "border-end-4 border-start-2");
/// ```
///
/// **Combinado (ejemplo completo):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let b = Border::with(BorderSize::Default) // Borde global por defecto.
/// .with_top(BorderSize::Zero) // Quita borde superior.
/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final.
/// .with_color(BorderColor::Theme(Color::Primary))
/// .with_opacity(BorderOpacity::Theme(Opacity::Half));
///
/// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50");
/// ```
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Border {
size : BorderSize,
top : BorderSize,
end : BorderSize,
bottom : BorderSize,
start : BorderSize,
color : BorderColor,
opacity: BorderOpacity,
}
impl Border {
/// Prepara un borde **sin tamaño global** de partida.
pub fn new() -> Self {
Self::default()
}
/// Crea un borde **con tamaño global** (`size`).
pub fn with(size: BorderSize) -> Self {
Self::default().with_size(size)
}
// **< Border BUILDER >*************************************************************************
/// Establece el tamaño global del borde (`border*`).
pub fn with_size(mut self, size: BorderSize) -> Self {
self.size = size;
self
}
/// Establece el tamaño del borde superior (`border-top-*`).
pub fn with_top(mut self, size: BorderSize) -> Self {
self.top = size;
self
}
/// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL.
pub fn with_end(mut self, size: BorderSize) -> Self {
self.end = size;
self
}
/// Establece el tamaño del borde inferior (`border-bottom-*`).
pub fn with_bottom(mut self, size: BorderSize) -> Self {
self.bottom = size;
self
}
/// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL.
pub fn with_start(mut self, size: BorderSize) -> Self {
self.start = size;
self
}
/// Establece el **color** del borde.
pub fn with_color(mut self, color: BorderColor) -> Self {
self.color = color;
self
}
/// Establece la **opacidad** del borde.
pub fn with_opacity(mut self, opacity: BorderOpacity) -> Self {
self.opacity = opacity;
self
}
}
impl fmt::Display for Border {
/// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`, *color* y
/// *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
join_opt!([
self.size.to_string(),
self.top.to_class("border-top"),
self.end.to_class("border-end"),
self.bottom.to_class("border-bottom"),
self.start.to_class("border-start"),
self.color.to_string(),
self.opacity.to_string(),
]; " ")
.unwrap_or_default()
)
}
}

View file

@ -0,0 +1,127 @@
use pagetop::prelude::*;
use std::fmt;
/// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*.
///
/// - `"sm"`, `"md"`, `"lg"`, `"xl"` o `"xxl"` para los puntos de ruptura `SM`, `MD`, `LG`, `XL` o
/// `XXL`, respectivamente.
/// - `""` (cadena vacía) para `None`.
/// - `"fluid"` para las variantes `Fluid` y `FluidMax(_)`, útil para modelar `container-fluid` en
/// [`Container`](crate::theme::Container). Se debe tener en cuenta que `"fluid"` **no** es un
/// sufijo de *breakpoint*. Para construir clases válidas con prefijo (p. ej., `"col-md"`), se
/// recomienda usar `to_class()` (Self::to_class) o `try_class()` (Self::try_class).
///
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// assert_eq!(BreakPoint::MD.to_string(), "md");
/// assert_eq!(BreakPoint::None.to_string(), "");
/// assert_eq!(BreakPoint::Fluid.to_string(), "fluid");
///
/// // Forma correcta para clases con prefijo:
/// //assert_eq!(BreakPoint::MD.to_class("col"), "col-md");
/// //assert_eq!(BreakPoint::Fluid.to_class("offcanvas"), "offcanvas");
/// ```
#[rustfmt::skip]
#[derive(AutoDefault)]
pub enum BreakPoint {
/// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical.
#[default]
None,
/// **576px o más** - Dispositivos pequeños: teléfonos en modo horizontal.
SM,
/// **768px o más** - Dispositivos medianos: tabletas.
MD,
/// **992px o más** - Dispositivos grandes: puestos de escritorio.
LG,
/// **1200px o más** - Dispositivos muy grandes: puestos de escritorio grandes.
XL,
/// **1400px o más** - Dispositivos extragrandes: puestos de escritorio más grandes.
XXL,
/// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo.
Fluid,
/// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo hasta
/// un ancho máximo indicado.
FluidMax(UnitValue)
}
impl BreakPoint {
/// Comprueba si es un punto de ruptura efectivo.
///
/// Devuelve `true` si el valor es `SM`, `MD`, `LG`, `XL` o `XXL`; y `false` en otro caso.
#[inline]
pub const fn is_breakpoint(&self) -> bool {
matches!(self, Self::SM | Self::MD | Self::LG | Self::XL | Self::XXL)
}
/// Genera un nombre de clase CSS basado en el punto de ruptura.
///
/// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), concatena
/// el prefijo, un guion (`-`) y el sufijo asociado. En otro caso devuelve únicamente el
/// prefijo.
///
/// # Ejemplo
///
/// ```rust
/// let breakpoint = BreakPoint::MD;
/// let class = breakpoint.to_class("col");
/// assert_eq!(class, "col-md".to_string());
///
/// let breakpoint = BreakPoint::Fluid;
/// let class = breakpoint.to_class("offcanvas");
/// assert_eq!(class, "offcanvas".to_string());
/// ```
#[inline]
pub fn to_class(&self, prefix: impl AsRef<str>) -> String {
if self.is_breakpoint() {
join!(prefix, "-", self.to_string())
} else {
String::from(prefix.as_ref())
}
}
/// Intenta generar un nombre de clase CSS basado en el punto de ruptura.
///
/// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), devuelve
/// `Some(String)` concatenando el prefijo, un guion (`-`) y el sufijo asociado. En otro caso
/// devuelve `None`.
///
/// # Ejemplo
///
/// ```rust
/// let breakpoint = BreakPoint::MD;
/// let class = breakpoint.try_class("col");
/// assert_eq!(class, Some("col-md".to_string()));
///
/// let breakpoint = BreakPoint::Fluid;
/// let class = breakpoint.try_class("navbar-expanded");
/// assert_eq!(class, None);
/// ```
#[inline]
pub fn try_class(&self, prefix: impl AsRef<str>) -> Option<String> {
if self.is_breakpoint() {
Some(join!(prefix, "-", self.to_string()))
} else {
None
}
}
}
#[rustfmt::skip]
impl fmt::Display for BreakPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => Ok(()),
Self::SM => f.write_str("sm"),
Self::MD => f.write_str("md"),
Self::LG => f.write_str("lg"),
Self::XL => f.write_str("xl"),
Self::XXL => f.write_str("xxl"),
// Devuelven "fluid" (para modelar `container-fluid`).
Self::Fluid => f.write_str("fluid"),
Self::FluidMax(_) => f.write_str("fluid"),
}
}
}

View file

@ -0,0 +1,155 @@
use pagetop::prelude::*;
use std::fmt;
// **< Color >**************************************************************************************
/// Paleta de colores **temáticos**.
///
/// Equivalente a los nombres estándar de Bootstrap (`primary`, `secondary`, `success`, etc.). Sirve
/// como base para componer clases de fondo ([`BgColor`]), borde ([`BorderColor`]) y texto
/// ([`TextColor`]).
#[derive(AutoDefault)]
pub enum Color {
#[default]
Primary,
Secondary,
Success,
Info,
Warning,
Danger,
Light,
Dark,
}
#[rustfmt::skip]
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Primary => f.write_str("primary"),
Self::Secondary => f.write_str("secondary"),
Self::Success => f.write_str("success"),
Self::Info => f.write_str("info"),
Self::Warning => f.write_str("warning"),
Self::Danger => f.write_str("danger"),
Self::Light => f.write_str("light"),
Self::Dark => f.write_str("dark"),
}
}
}
// **< BgColor >************************************************************************************
/// Colores de fondo (`bg-*`).
///
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
/// - `Body*` usa fondos predefinidos del tema (`bg-body`, `bg-body-secondary`, `bg-body-tertiary`).
/// - `Theme(Color)` genera `bg-{color}` (p. ej., `bg-primary`).
/// - `Subtle(Color)` genera `bg-{color}-subtle` (tono suave).
/// - `Black` y `White` son colores explícitos.
/// - `Transparent` no aplica color de fondo (`bg-transparent`).
#[derive(AutoDefault)]
pub enum BgColor {
#[default]
Default,
Body,
BodySecondary,
BodyTertiary,
Theme(Color),
Subtle(Color),
Black,
White,
Transparent,
}
#[rustfmt::skip]
impl fmt::Display for BgColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => Ok(()),
Self::Body => f.write_str("bg-body"),
Self::BodySecondary => f.write_str("bg-body-secondary"),
Self::BodyTertiary => f.write_str("bg-body-tertiary"),
Self::Theme(c) => write!(f, "bg-{c}"),
Self::Subtle(c) => write!(f, "bg-{c}-subtle"),
Self::Black => f.write_str("bg-black"),
Self::White => f.write_str("bg-white"),
Self::Transparent => f.write_str("bg-transparent"),
}
}
}
// **< BorderColor >********************************************************************************
/// Colores (`border-*`) para los bordes ([`Border`](crate::theme::aux::Border)).
///
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
/// - `Theme(Color)` genera `border-{color}`.
/// - `Subtle(Color)` genera `border-{color}-subtle` (versión suavizada).
/// - `Black` y `White` son colores explícitos.
#[derive(AutoDefault)]
pub enum BorderColor {
#[default]
Default,
Theme(Color),
Subtle(Color),
Black,
White,
}
#[rustfmt::skip]
impl fmt::Display for BorderColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => Ok(()),
Self::Theme(c) => write!(f, "border-{c}"),
Self::Subtle(c) => write!(f, "border-{c}-subtle"),
Self::Black => f.write_str("border-black"),
Self::White => f.write_str("border-white"),
}
}
}
// **< TextColor >**********************************************************************************
/// Colores de texto y fondos de texto (`text-*`).
///
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
/// - `Body*` aplica colores predefinidos del tema (`text-body`, `text-body-emphasis`,
/// `text-body-secondary`, `text-body-tertiary`).
/// - `Theme(Color)` genera `text-{color}`.
/// - `Emphasis(Color)` genera `text-{color}-emphasis` (contraste mayor acorde al tema).
/// - `Background(Color)` genera `text-bg-{color}` (para color de fondo del texto).
/// - `Black` y `White` son colores explícitos.
#[derive(AutoDefault)]
pub enum TextColor {
#[default]
Default,
Body,
BodyEmphasis,
BodySecondary,
BodyTertiary,
Theme(Color),
Emphasis(Color),
Background(Color),
Black,
White,
}
#[rustfmt::skip]
impl fmt::Display for TextColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => Ok(()),
Self::Body => f.write_str("text-body"),
Self::BodyEmphasis => f.write_str("text-body-emphasis"),
Self::BodySecondary => f.write_str("text-body-secondary"),
Self::BodyTertiary => f.write_str("text-body-tertiary"),
Self::Theme(c) => write!(f, "text-{c}"),
Self::Emphasis(c) => write!(f, "text-{c}-emphasis"),
Self::Background(c) => write!(f, "text-bg-{c}"),
Self::Black => f.write_str("text-black"),
Self::White => f.write_str("text-white"),
}
}
}

View file

@ -0,0 +1,109 @@
use pagetop::prelude::*;
use std::fmt;
// **< Opacity >************************************************************************************
/// Niveles de **opacidad** (`opacity-*`).
///
/// Se usa para modular la transparencia del color de fondo `bg-opacity-*` ([`BgOpacity`]), borde
/// `border-opacity-*` ([`BorderOpacity`]) o texto `text-opacity-*` ([`TextOpacity`]), según las
/// siguientes equivalencias:
///
/// - `Opaque` => `opacity-100` (100% de opacidad).
/// - `SemiOpaque` => `opacity-75` (75%).
/// - `Half` => `opacity-50` (50%).
/// - `SemiTransparent` => `opacity-25` (25%).
/// - `AlmostTransparent` => `opacity-10` (10%).
/// - `Transparent` => `opacity-0` (0%, totalmente transparente).
#[rustfmt::skip]
#[derive(AutoDefault)]
pub enum Opacity {
#[default]
Opaque, // 100%
SemiOpaque, // 75%
Half, // 50%
SemiTransparent, // 25%
AlmostTransparent, // 10%
Transparent, // 0%
}
#[rustfmt::skip]
impl fmt::Display for Opacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Opaque => f.write_str("opacity-100"),
Self::SemiOpaque => f.write_str("opacity-75"),
Self::Half => f.write_str("opacity-50"),
Self::SemiTransparent => f.write_str("opacity-25"),
Self::AlmostTransparent => f.write_str("opacity-10"),
Self::Transparent => f.write_str("opacity-0"),
}
}
}
// **< BgOpacity >**********************************************************************************
/// Opacidad para el fondo (`bg-opacity-*`).
///
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
/// - `Theme(Opacity)` genera `bg-{opacity}` (p. ej., `bg-opacity-50`).
#[derive(AutoDefault)]
pub enum BgOpacity {
#[default]
Default,
Theme(Opacity),
}
impl fmt::Display for BgOpacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => Ok(()),
Self::Theme(o) => write!(f, "bg-{o}"),
}
}
}
// **< BorderOpacity >******************************************************************************
/// Opacidad (`border-opacity-*`) para los bordes ([`Border`](crate::theme::aux::Border)).
///
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
/// - `Theme(Opacity)` genera `border-{opacity}` (p. ej., `border-opacity-25`).
#[derive(AutoDefault)]
pub enum BorderOpacity {
#[default]
Default,
Theme(Opacity),
}
impl fmt::Display for BorderOpacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => Ok(()),
Self::Theme(o) => write!(f, "border-{o}"),
}
}
}
// **< TextOpacity >********************************************************************************
/// Opacidad para el texto (`text-opacity-*`).
///
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
/// - `Theme(Opacity)` genera `text-{opacity}` (p. ej., `text-opacity-100`).
#[derive(AutoDefault)]
pub enum TextOpacity {
#[default]
Default,
Theme(Opacity),
}
impl fmt::Display for TextOpacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => Ok(()),
Self::Theme(o) => write!(f, "text-{o}"),
}
}
}

View file

@ -0,0 +1,214 @@
use pagetop::prelude::*;
use std::fmt;
// **< RoundedRadius >******************************************************************************
/// Radio (**redondeo**) para las esquinas ([`Rounded`]).
///
/// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`.
///
/// - `None` no añade clase (devuelve `""`).
/// - `Default` genera `rounded` (radio por defecto del tema).
/// - `Zero` genera `rounded-0` (sin redondeo).
/// - `Scale{1..5}` genera `rounded-{1..5}` (radio creciente).
/// - `Circle` genera `rounded-circle`.
/// - `Pill` genera `rounded-pill`.
#[derive(AutoDefault)]
pub enum RoundedRadius {
#[default]
None,
Default,
Zero,
Scale1,
Scale2,
Scale3,
Scale4,
Scale5,
Circle,
Pill,
}
impl RoundedRadius {
#[rustfmt::skip]
fn to_class(&self, base: impl AsRef<str>) -> String {
match self {
RoundedRadius::None => String::new(),
RoundedRadius::Default => String::from(base.as_ref()),
RoundedRadius::Zero => join!(base, "-0"),
RoundedRadius::Scale1 => join!(base, "-1"),
RoundedRadius::Scale2 => join!(base, "-2"),
RoundedRadius::Scale3 => join!(base, "-3"),
RoundedRadius::Scale4 => join!(base, "-4"),
RoundedRadius::Scale5 => join!(base, "-5"),
RoundedRadius::Circle => join!(base, "-circle"),
RoundedRadius::Pill => join!(base, "-pill"),
}
}
}
impl fmt::Display for RoundedRadius {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_class("rounded"))
}
}
// **< Rounded >************************************************************************************
/// Agrupa propiedades para crear **esquinas redondeadas**.
///
/// Permite:
///
/// - Definir un radio **global para todas las esquinas** (`radius`).
/// - Ajustar el radio asociado a las **esquinas de cada lado lógico** (`top`, `end`, `bottom`,
/// `start`, **en este orden**, respetando LTR/RTL).
/// - Ajustar el radio de las **esquinas concretas** (`top-start`, `top-end`, `bottom-start`,
/// `bottom-end`, **en este orden**, respetando LTR/RTL).
///
/// # Ejemplos
///
/// **Radio global:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let r = Rounded::with(RoundedRadius::Default);
/// assert_eq!(r.to_string(), "rounded");
/// ```
///
/// **Sin redondeo:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let r = Rounded::new();
/// assert_eq!(r.to_string(), "");
/// ```
///
/// **Radio en las esquinas de un lado lógico:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let r = Rounded::new().with_end(RoundedRadius::Scale2);
/// assert_eq!(r.to_string(), "rounded-end-2");
/// ```
///
/// **Radio en una esquina concreta:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let r = Rounded::new().with_top_start(RoundedRadius::Scale3);
/// assert_eq!(r.to_string(), "rounded-top-start-3");
/// ```
///
/// **Combinado (ejemplo completo):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let r = Rounded::new()
/// .with_top(RoundedRadius::Default) // Añade redondeo arriba.
/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta.
/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina.
///
/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle");
/// ```
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Rounded {
radius : RoundedRadius,
top : RoundedRadius,
end : RoundedRadius,
bottom : RoundedRadius,
start : RoundedRadius,
top_start : RoundedRadius,
top_end : RoundedRadius,
bottom_start: RoundedRadius,
bottom_end : RoundedRadius,
}
impl Rounded {
/// Prepara las esquinas **sin redondeo global** de partida.
pub fn new() -> Self {
Self::default()
}
/// Crea las esquinas **con redondeo global** (`radius`).
pub fn with(radius: RoundedRadius) -> Self {
Self::default().with_radius(radius)
}
// **< Rounded BUILDER >************************************************************************
/// Establece el radio global de las esquinas (`rounded*`).
pub fn with_radius(mut self, radius: RoundedRadius) -> Self {
self.radius = radius;
self
}
/// Establece el radio en las esquinas del lado superior (`rounded-top-*`).
pub fn with_top(mut self, radius: RoundedRadius) -> Self {
self.top = radius;
self
}
/// Establece el radio en las esquinas del lado lógico final (`rounded-end-*`). Respeta LTR/RTL.
pub fn with_end(mut self, radius: RoundedRadius) -> Self {
self.end = radius;
self
}
/// Establece el radio en las esquinas del lado inferior (`rounded-bottom-*`).
pub fn with_bottom(mut self, radius: RoundedRadius) -> Self {
self.bottom = radius;
self
}
/// Establece el radio en las esquinas del lado lógico inicial (`rounded-start-*`). Respeta
/// LTR/RTL.
pub fn with_start(mut self, radius: RoundedRadius) -> Self {
self.start = radius;
self
}
/// Establece el radio en la esquina superior-inicial (`rounded-top-start-*`). Respeta LTR/RTL.
pub fn with_top_start(mut self, radius: RoundedRadius) -> Self {
self.top_start = radius;
self
}
/// Establece el radio en la esquina superior-final (`rounded-top-end-*`). Respeta LTR/RTL.
pub fn with_top_end(mut self, radius: RoundedRadius) -> Self {
self.top_end = radius;
self
}
/// Establece el radio en la esquina inferior-inicial (`rounded-bottom-start-*`). Respeta
/// LTR/RTL.
pub fn with_bottom_start(mut self, radius: RoundedRadius) -> Self {
self.bottom_start = radius;
self
}
/// Establece el radio en la esquina inferior-final (`rounded-bottom-end-*`). Respeta LTR/RTL.
pub fn with_bottom_end(mut self, radius: RoundedRadius) -> Self {
self.bottom_end = radius;
self
}
}
impl fmt::Display for Rounded {
/// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`,
/// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las
/// definiciones vacías.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
join_opt!([
self.radius.to_string(),
self.top.to_class("rounded-top"),
self.end.to_class("rounded-end"),
self.bottom.to_class("rounded-bottom"),
self.start.to_class("rounded-start"),
self.top_start.to_class("rounded-top-start"),
self.top_end.to_class("rounded-top-end"),
self.bottom_start.to_class("rounded-bottom-start"),
self.bottom_end.to_class("rounded-bottom-end"),
]; " ")
.unwrap_or_default()
)
}
}