First commit. Version 0.1.0

This commit is contained in:
2026-05-10 09:58:23 +02:00
parent af105b7f7d
commit bbab5e238d
635 changed files with 53627 additions and 175 deletions

107
src/theme/surface.rs Normal file
View File

@@ -0,0 +1,107 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! Composite surfaces: fill + outer shadows + inset shadows.
//!
//! A [`Surface`] is the richest kind of theme slot. It packages together a
//! fill (possibly a gradient), an elevation stack, and inset shadows with
//! their own blend modes. Widgets that only need a flat colour or a plain
//! gradient do not construct [`Surface`] values directly; slot promotion
//! (in `theme::slots`) hands them a [`Surface`] with empty decorations when
//! they ask for one against a simpler slot.
use super::paint::Paint;
use super::shadow::{ InsetShadow, ShadowsRef };
use crate::types::Color;
// ─── Surface ─────────────────────────────────────────────────────────────────
/// A composite theme surface: fill, elevation, insets.
///
/// All decorations are optional. A surface with `fill: Paint::Solid(...)`,
/// no shadows and no insets behaves exactly like a flat-colour fill, which
/// is why the promotion path from a `color` slot to a [`Surface`] is trivial.
#[ derive( Debug, Clone, PartialEq ) ]
pub struct Surface
{
/// What the surface is filled with.
pub fill: Paint,
/// Outer shadow stack. When `Some`, can reference another slot by name
/// or inline the full list.
pub shadows: Option<ShadowsRef>,
/// Inset shadows, in back-to-front order. Each entry carries its own
/// [`crate::theme::BlendMode`].
pub inset_shadows: Vec<InsetShadow>,
}
impl Surface
{
/// Build a surface from just a [`Paint`], with no shadows or insets.
pub fn from_paint( paint: Paint ) -> Self
{
Self
{
fill: paint,
shadows: None,
inset_shadows: Vec::new(),
}
}
}
impl From<Paint> for Surface
{
fn from( p: Paint ) -> Self { Surface::from_paint( p ) }
}
impl From<Color> for Surface
{
fn from( c: Color ) -> Self { Surface::from_paint( Paint::Solid( c ) ) }
}
// ─── Tests ───────────────────────────────────────────────────────────────────
#[ cfg( test ) ]
mod tests
{
use super::*;
use crate::types::Color;
use crate::theme::shadow::{ BlendMode, InsetShadow, Shadow, ShadowsRef };
#[ test ]
fn surface_promotes_from_color()
{
let s: Surface = Color::hex( 0x04, 0xD9, 0xFE ).into();
assert_eq!( s.fill, Paint::Solid( Color::hex( 0x04, 0xD9, 0xFE ) ) );
assert!( s.shadows.is_none() );
assert!( s.inset_shadows.is_empty() );
}
#[ test ]
fn glass_accent_shape_composes()
{
let s = Surface
{
fill: Paint::Solid( Color::hex( 0x04, 0xD9, 0xFE ) ),
shadows: Some( ShadowsRef::Named( "shadows-glass".to_string() ) ),
inset_shadows: vec!
[
InsetShadow { offset: [ -3.6, -3.6 ], blur: 13.5, spread: 0.0, color: Color::hex( 0x55, 0x55, 0x55 ), blend: BlendMode::PlusLighter },
InsetShadow { offset: [ 1.8, 1.8 ], blur: 1.8, spread: 0.0, color: Color::hex( 0x55, 0x55, 0x55 ), blend: BlendMode::PlusLighter },
InsetShadow { offset: [ 0.45, 0.45 ], blur: 0.9, spread: 0.0, color: Color::BLACK, blend: BlendMode::Overlay },
InsetShadow { offset: [ 1.8, 1.8 ], blur: 7.2, spread: 0.0, color: Color::rgba( 0.0, 0.0, 0.0, 0.15 ), blend: BlendMode::Normal },
],
};
assert_eq!( s.inset_shadows.len(), 4 );
assert_eq!( s.inset_shadows[0].blend, BlendMode::PlusLighter );
assert_eq!( s.inset_shadows[2].blend, BlendMode::Overlay );
let _ = Shadow
{
offset: [ 0.0, 0.0 ],
blur: 9.0,
spread: 0.0,
color: Color::rgba( 33.0 / 255.0, 33.0 / 255.0, 33.0 / 255.0, 0.25 ),
blend: BlendMode::Normal,
};
}
}