Commit 65e130dd authored by Florian Blasius's avatar Florian Blasius 🤘

Merge branch 'window-refactoring' into 'master'

Start: Split orbtk into stand alone sub crates

See merge request redox-os/orbtk!108
parents 107003ba 4022ecd9
This diff is collapsed.
......@@ -21,17 +21,28 @@ edition = "2018"
name = "orbtk"
path = "src/lib.rs"
[target.wasm32-unknown-unknown.dependencies]
orbtk-web-window = { version = "0.1.0", path = "crates/web-window" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
orbgl = { git = "https://gitlab.redox-os.org/redox-os/orbgl.git" }
orbfont = { git = "https://gitlab.redox-os.org/redox-os/orbfont.git" }
orbimage = "0.1.17"
orbclient = "0.3.21"
[dependencies]
cssparser = "0.17.0"
lazy_static = "1.2.0"
orbtk-structs = { version = "0.1.0", path = "crates/structs" }
orbtk-css-engine = { version = "0.1.0", path = "crates/css-engine" }
lazy_static = "1.3.0"
# dces = { path = "../dces-rust"}
dces = { git = "https://gitlab.redox-os.org/redox-os/dces-rust.git" }
orbclient = "0.3.20"
orbfont = { git = "https://gitlab.redox-os.org/redox-os/orbfont.git" }
orbimage = "0.1.16"
# orbgl_api = { path = "../orbgl/orbgl_api" }
orbgl_api = { git = "https://gitlab.redox-os.org/redox-os/orbgl.git" }
# orbgl = { path = "../orbgl/orbgl" }
orbgl = { git = "https://gitlab.redox-os.org/redox-os/orbgl.git" }
# orbgl_shapes = { path = "../orbgl/orbgl_shapes" }
orbgl_shapes = { git = "https://gitlab.redox-os.org/redox-os/orbgl.git" }
[workspace]
members = [
"crates/css-engine",
"crates/structs",
"crates/web-window"
]
\ No newline at end of file
[package]
name = "orbtk-css-engine"
version = "0.1.0"
authors = ["Florian Blasius <flovanpt@posteo.de>"]
description = "CSS engine used to theme OrbTk."
repository = "https://gitlab.redox-os.org/redox-os/orbtk"
license = "MIT"
keywords = ["css", "theme", "styling", "ui"]
edition = "2018"
[dependencies]
orbtk-structs = { version = "0.1.0", path = "../structs" }
cssparser = "0.17.0"
\ No newline at end of file
pub use selector::*;
pub use theme::*;
pub mod prelude;
mod selector;
mod theme;
pub use crate::*;
\ No newline at end of file
use std::{collections::HashSet, ops::Add};
use std::fmt;
#[derive(Clone, Debug)]
pub enum SelectorRelation {
Ancestor(Selector),
Parent(Selector),
}
/// Describes the specificity of a selector.
///
/// The indexes are as follows:
/// 0 - number of IDs (most important)
/// 1 - number of classes and pseudo-classes
/// 2 - number of elements (least important)
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Specificity([u8; 3]);
impl Add<Self> for Specificity {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Specificity([
self.0[0] + rhs.0[0],
self.0[1] + rhs.0[1],
self.0[2] + rhs.0[2],
])
}
}
/// Describes a css selector.
#[derive(Debug, Default)]
pub struct Selector {
pub id: Option<String>,
pub element: Option<String>,
pub classes: HashSet<String>,
pub pseudo_classes: HashSet<String>,
pub relation: Option<Box<SelectorRelation>>,
pub dirty: bool,
}
/// Inner selector value.
impl Selector {
pub fn new() -> Self {
Selector {
id: None,
element: None,
classes: HashSet::new(),
pseudo_classes: HashSet::new(),
relation: None,
dirty: true,
}
}
pub fn is_empty(&self) -> bool {
self.element.is_none()
&& self.id.is_none()
&& self.classes.is_empty()
&& self.pseudo_classes.is_empty()
}
pub fn dirty(&self) -> bool {
self.dirty
}
pub fn set_dirty(&mut self, dirty: bool) {
self.dirty = dirty;
}
pub fn specificity(&self) -> Specificity {
let s = Specificity([
if self.id.is_some() { 1 } else { 0 },
(self.classes.len() + self.pseudo_classes.len()) as u8,
if self.element.is_some() { 1 } else { 0 },
]);
if let Some(ref relation) = self.relation {
match **relation {
SelectorRelation::Ancestor(ref x) | SelectorRelation::Parent(ref x) => {
return x.specificity() + s;
}
}
}
s
}
pub fn matches(&self, other: &Selector) -> bool {
if self.id.is_some() && self.id != other.id {
return false;
}
if self.element.is_some() && self.element != other.element {
return false;
}
if !other.classes.is_superset(&self.classes) {
return false;
}
if !other.pseudo_classes.is_superset(&self.pseudo_classes) {
return false;
}
true
}
pub fn with<S: Into<String>>(mut self, element: S) -> Self {
self.element = Some(element.into());
self
}
pub fn id<S: Into<String>>(mut self, id: S) -> Self {
self.id = Some(id.into());
self
}
pub fn class<S: Into<String>>(mut self, class: S) -> Self {
self.classes.insert(class.into());
self
}
pub fn without_class<S: Into<String>>(mut self, class: S) -> Self {
self.classes.remove(&class.into());
self
}
pub fn pseudo_class<S: Into<String>>(mut self, pseudo_class: S) -> Self {
self.pseudo_classes.insert(pseudo_class.into());
self
}
pub fn without_pseudo_class<S: Into<String>>(mut self, pseudo_class: S) -> Self {
self.pseudo_classes.remove(&pseudo_class.into());
self
}
}
impl PartialEq for Selector {
fn eq(&self, other: &Selector) -> bool {
self.id == other.id
}
}
impl Clone for Selector {
fn clone(&self) -> Self {
Selector {
id: self.id.clone(),
element: self.element.clone(),
classes: self.classes.clone(),
pseudo_classes: self.pseudo_classes.clone(),
relation: self.relation.clone(),
dirty: self.dirty,
}
}
}
impl fmt::Display for Selector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(element) = &self.element {
return write!(f, ", css: {}", element);
}
write!(f, "")
}
}
\ No newline at end of file
......@@ -7,29 +7,9 @@ use cssparser::{
Token,
};
use crate::{
properties::*,
structs::{Brush, Spacer},
styling::{
fonts,
theme::{DEFAULT_THEME_CSS, LIGHT_THEME_EXTENSION_CSS},
},
widgets::WidgetContainer,
};
use orbtk_structs::prelude::*;
lazy_static! {
static ref DEFAULT_THEME: Arc<Theme> = {
Arc::new(Theme {
parent: None,
rules: parse(DEFAULT_THEME_CSS),
})
};
}
lazy_static! {
pub static ref LIGHT_THEME_CSS: String =
format!("{}{}", LIGHT_THEME_EXTENSION_CSS, DEFAULT_THEME_CSS);
}
use crate::prelude::*;
/// Used to build a theme, specifying additional details.
pub struct ThemeBuilder {
......@@ -106,36 +86,13 @@ pub struct Theme {
rules: Vec<Rule>,
}
impl Default for Theme {
fn default() -> Theme {
Theme::parse(DEFAULT_THEME_CSS)
}
}
impl Theme {
/// Returns the default light theme.
pub fn default_light() -> Theme {
Theme::parse(&format!(
"{}{}",
LIGHT_THEME_EXTENSION_CSS, DEFAULT_THEME_CSS
))
}
/// Creates a new `ThemeBuilder` object with default theme as base.
pub fn create() -> ThemeBuilder {
ThemeBuilder {
theme_css: Some(DEFAULT_THEME_CSS.to_string()),
..Default::default()
}
}
/// Creates a new `ThemeBuilder` object with light theme as base.
pub fn create_light_theme() -> ThemeBuilder {
ThemeBuilder {
theme_css: Some(format!(
"{}{}",
LIGHT_THEME_EXTENSION_CSS, DEFAULT_THEME_CSS
)),
theme_css: None,
..Default::default()
}
}
......@@ -158,7 +115,7 @@ impl Theme {
fn parse(s: &str) -> Self {
Theme {
parent: Some(DEFAULT_THEME.clone()),
parent: None,
rules: parse(s),
}
}
......@@ -195,7 +152,7 @@ impl Theme {
let matching_selectors = rule
.selectors
.iter()
.filter(|x| x.0.matches(&query.0))
.filter(|x| x.matches(&query))
.collect::<Vec<_>>();
if !matching_selectors.is_empty() {
......@@ -206,7 +163,7 @@ impl Theme {
{
let highest_specifity = matching_selectors
.iter()
.map(|sel| sel.0.specificity())
.map(|sel| sel.specificity())
.max()
.unwrap();
matches.push((decl.important, highest_specifity, decl.value.clone()));
......@@ -233,117 +190,6 @@ impl Theme {
pub fn string(&self, property: &str, query: &Selector) -> Option<String> {
self.get(property, query).and_then(|v| v.string())
}
/// Updates the given widget by theme and selector.
pub fn update_widget_theme(&self, widget: &mut WidgetContainer) {
if !widget.has::<Selector>() {
return;
}
let selector = widget.clone::<Selector>();
if !selector.0.dirty() {
return;
}
if let Some(foreground) = widget.try_get_mut::<Foreground>() {
if let Some(color) = self.brush("color", &selector) {
foreground.0 = color;
}
}
if let Some(background) = widget.try_get_mut::<Background>() {
if let Some(bg) = self.brush("background", &selector) {
background.0 = bg;
}
}
if let Some(border_brush) = widget.try_get_mut::<BorderBrush>() {
if let Some(border_color) = self.brush("border-color", &selector) {
border_brush.0 = border_color;
}
}
if let Some(border_radius) = widget.try_get_mut::<BorderRadius>() {
if let Some(radius) = self.float("border-radius", &selector) {
border_radius.0 = radius as f64;
}
}
if let Some(border_thickness) = widget.try_get_mut::<BorderThickness>() {
if let Some(border_width) = self.uint("border-width", &selector) {
*border_thickness = BorderThickness::from(border_width as f64);
}
}
if let Some(font_size) = widget.try_get_mut::<FontSize>() {
if let Some(size) = self.uint("font-size", &selector) {
font_size.0 = size as f64;
}
}
if let Some(font) = widget.try_get_mut::<Font>() {
if let Some(font_family) = self.string("font-family", &selector) {
if let Some(inner_font) = fonts::font_by_key(&font_family[..]) {
(font.0).0 = inner_font;
}
}
}
if let Some(icon_brush) = widget.try_get_mut::<IconBrush>() {
if let Some(color) = self.brush("icon-color", &selector) {
icon_brush.0 = color;
}
}
if let Some(icon_size) = widget.try_get_mut::<IconSize>() {
if let Some(size) = self.uint("icon-size", &selector) {
icon_size.0 = size as f64;
}
}
if let Some(icon_font) = widget.try_get_mut::<IconFont>() {
if let Some(font_family) = self.string("icon-family", &selector) {
if let Some(inner_font) = fonts::font_by_key(&font_family[..]) {
(icon_font.0).0 = inner_font;
}
}
}
if let Some(padding) = widget.try_get_mut::<Padding>() {
if let Some(pad) = self.uint("padding", &selector) {
padding.set_thickness(pad as f64);
}
}
if let Some(padding) = widget.try_get_mut::<Padding>() {
if let Some(left) = self.uint("padding-left", &selector) {
padding.set_left(left as f64);
}
}
if let Some(padding) = widget.try_get_mut::<Padding>() {
if let Some(top) = self.uint("padding-top", &selector) {
padding.set_top(top as f64);
}
}
if let Some(padding) = widget.try_get_mut::<Padding>() {
if let Some(right) = self.uint("padding-right", &selector) {
padding.set_right(right as f64);
}
}
if let Some(padding) = widget.try_get_mut::<Padding>() {
if let Some(bottom) = self.uint("padding-bottom", &selector) {
padding.set_bottom(bottom as f64);
}
}
// todo padding, icon_margin
widget.get_mut::<Selector>().0.set_dirty(true);
}
}
#[derive(Clone, Debug)]
......@@ -473,7 +319,7 @@ fn parse_selectors<'i, 't>(
) -> Result<Vec<Selector>, ParseError<'i, CustomParseError>> {
let mut selectors = Vec::new();
let mut selector = SelectorValue::default();
let mut selector = Selector::default();
let mut first_token_in_selector = true;
while let Ok(t) = input.next() {
......@@ -483,14 +329,14 @@ fn parse_selectors<'i, 't>(
if first_token_in_selector {
selector.element = Some(element_name.to_string())
} else {
let mut old_selector = SelectorValue::new().with(element_name.to_string());
let mut old_selector = Selector::new().with(element_name.to_string());
mem::swap(&mut old_selector, &mut selector);
selector.relation = Some(Box::new(SelectorRelation::Ancestor(old_selector)));
}
}
Token::Delim('>') => {
let mut old_selector = SelectorValue::new().with(input.expect_ident()?.to_string());
let mut old_selector = Selector::new().with(input.expect_ident()?.to_string());
mem::swap(&mut old_selector, &mut selector);
selector.relation = Some(Box::new(SelectorRelation::Parent(old_selector)));
}
......@@ -518,7 +364,7 @@ fn parse_selectors<'i, 't>(
// This selector is done, on to the next one
Token::Comma => {
selectors.push(Selector::from(selector));
selector = SelectorValue::default();
selector = Selector::default();
first_token_in_selector = true;
continue; // need to continue to avoid `first_token_in_selector` being set to false
}
......@@ -534,7 +380,7 @@ fn parse_selectors<'i, 't>(
selectors.push(Selector::from(selector));
if selectors.iter().any(|sel| sel.0.relation.is_some()) {
if selectors.iter().any(|sel| sel.relation.is_some()) {
eprintln!("WARNING: Complex selector relations not implemented");
}
......
[package]
name = "orbtk-structs"
version = "0.1.0"
authors = ["Florian Blasius <flovanpt@posteo.de>"]
description = "Helper structs and traits for OrbTk."
repository = "https://gitlab.redox-os.org/redox-os/orbtk"
license = "MIT"
keywords = ["helpers", "structs", "traits", "ui"]
edition = "2018"
[dependencies]
orbgl_api = { git = "https://gitlab.redox-os.org/redox-os/orbgl.git" }
\ No newline at end of file
pub use self::border::*;
pub use self::brush::*;
pub use self::dirty_size::*;
pub use self::point::*;
pub use self::rect::*;
pub use self::thickness::*;
pub use orbgl_api::Color as Color;
mod border;
mod brush;
mod dirty_size;
mod point;
pub mod prelude;
mod rect;
mod spacer;
mod thickness;
#[cfg(test)]
mod tests;
\ No newline at end of file
pub use crate::*;
\ No newline at end of file
pub use orbclient::Color;
pub use self::border::*;
pub use self::brush::*;
pub use self::dirty_size::DirtySize;
pub use self::point::Point;
pub use self::rect::*;
pub use self::thickness::Thickness;
mod border;
mod brush;
mod dirty_size;
mod point;
mod rect;
mod thickness;
use crate::Thickness;
// todo: documentation
pub trait Spacer {
......@@ -47,5 +33,4 @@ pub trait Spacer {
fn set_thickness<T: Into<Thickness>>(&mut self, thickness: T);
}
#[cfg(test)]
mod tests;
use super::Spacer;
/// Used to describes a thickness e.g a border thickness.
#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct Thickness {
......@@ -94,3 +92,36 @@ impl From<f64> for Thickness {
Thickness::new(t, t, t, t)
}
}
// todo: documentation
pub trait Spacer {
/// Gets left.
fn left(&self) -> f64;
/// Sets left.
fn set_left(&mut self, left: f64);
/// Gets top.
fn top(&self) -> f64;
/// Sets top.
fn set_top(&mut self, top: f64);
/// Gets right.
fn right(&self) -> f64;
/// Sets right.
fn set_right(&mut self, right: f64);
/// Gets bottom.
fn bottom(&self) -> f64;
/// Sets bottom.
fn set_bottom(&mut self, bottom: f64);
/// Gets thickness.
fn thickness(&self) -> Thickness;
/// Sets thickness.
fn set_thickness<T: Into<Thickness>>(&mut self, thickness: T);
}
\ No newline at end of file
[package]
name = "orbtk-web-window"
version = "0.1.0"
authors = ["Florian Blasius <flovanpt@posteo.de>"]
description = "OrbTk window library for wasm."
repository = "https://gitlab.redox-os.org/redox-os/orbtk"
license = "MIT"
keywords = ["windowing", "wasm"]
edition = "2018"
[dependencies]
stdweb = "0.4.16"
orbtk-structs = { version = "0.1.0", path = "../structs" }
use stdweb::web::event::*;
/// Defines web mouse events.
pub enum Mouse {
Up(MouseUpEvent),
Down(MouseDownEvent),
Move(MouseMoveEvent)
}
/// Defines web key events.
pub enum Key {
Up(KeyUpEvent),
Down(KeyDownEvent)
}
/// Defines a web window event.
pub enum Event {
Mouse(Mouse),
Key(Key)
}
use std::{
rc::Rc,
cell::RefCell,
};
use stdweb::{
self, _js_impl, js,
traits::*,
unstable::TryInto,
web::{
self, document, event,
html_element::{CanvasElement, ImageElement},
window, CanvasRenderingContext2d, FillRule,
},
};
use orbtk_structs::{Position, Size};
pub use events::*;
/// Used to initializes the web engine.
///
/// This method must be called first!
pub fn initialize() {
stdweb::initialize();
}
/// Used to build a new web window.
#[derive(Default, Debug)]
pub struct WebWindowBuilder {
title: String,
size: (f64, f64),
}
impl WebWindowBuilder {
/// Creates a new web window builder with default values.
pub fn new() -> Self {
WebWindowBuilder::default()
}
/// Used to set the `title` of the window.
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
/// Used to set the size of the window.
pub fn size(mut self, width: f64, height: f64) -> Self {
self.size.0 = width;
self.size.1 = height;
self
}
pub fn build(self) -> WebWindow {
document().set_title(&self.title[..]);
let canvas: CanvasElement = document()
.create_element("canvas")
.unwrap()