♻️ Major code restructuring
This commit is contained in:
parent
a96e203bb3
commit
fa66d628a0
221 changed files with 228 additions and 315 deletions
10
src/core/action.rs
Normal file
10
src/core/action.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
mod definition;
|
||||
pub use definition::{action_ref, ActionBase, ActionTrait};
|
||||
|
||||
mod list;
|
||||
pub use list::Action;
|
||||
use list::ActionsList;
|
||||
|
||||
mod all;
|
||||
pub(crate) use all::add_action;
|
||||
pub use all::{dispatch_actions, KeyAction};
|
||||
34
src/core/action/all.rs
Normal file
34
src/core/action/all.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use crate::core::action::{Action, ActionsList};
|
||||
use crate::{Handle, LazyStatic};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub type KeyAction = (Handle, Option<Handle>, Option<String>);
|
||||
|
||||
// Registered actions.
|
||||
static ACTIONS: LazyStatic<RwLock<HashMap<KeyAction, ActionsList>>> =
|
||||
LazyStatic::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
pub fn add_action(action: Action) {
|
||||
let mut actions = ACTIONS.write().unwrap();
|
||||
let key_action = (
|
||||
action.handle(),
|
||||
action.referer_handle(),
|
||||
action.referer_id(),
|
||||
);
|
||||
if let Some(list) = actions.get_mut(&key_action) {
|
||||
list.add(action);
|
||||
} else {
|
||||
actions.insert(key_action, ActionsList::new(action));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_actions<B, F>(key_action: KeyAction, f: F)
|
||||
where
|
||||
F: FnMut(&Action) -> B,
|
||||
{
|
||||
if let Some(list) = ACTIONS.read().unwrap().get(&key_action) {
|
||||
list.iter_map(f)
|
||||
}
|
||||
}
|
||||
31
src/core/action/definition.rs
Normal file
31
src/core/action/definition.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use crate::{Handle, ImplementHandle, Weight};
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
pub trait ActionBase: Any {
|
||||
fn as_ref_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub trait ActionTrait: ActionBase + ImplementHandle + Send + Sync {
|
||||
fn referer_handle(&self) -> Option<Handle> {
|
||||
None
|
||||
}
|
||||
|
||||
fn referer_id(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ActionTrait> ActionBase for C {
|
||||
fn as_ref_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action_ref<A: 'static>(action: &dyn ActionTrait) -> &A {
|
||||
action.as_ref_any().downcast_ref::<A>().unwrap()
|
||||
}
|
||||
45
src/core/action/list.rs
Normal file
45
src/core/action/list.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use crate::core::action::ActionTrait;
|
||||
use crate::SmartDefault;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
pub type Action = Box<dyn ActionTrait>;
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct ActionsList(Arc<RwLock<Vec<Action>>>);
|
||||
|
||||
impl ActionsList {
|
||||
pub fn new(action: Action) -> Self {
|
||||
let mut list = ActionsList::default();
|
||||
list.add(action);
|
||||
list
|
||||
}
|
||||
|
||||
pub fn add(&mut self, action: Action) {
|
||||
let mut list = self.0.write().unwrap();
|
||||
list.push(action);
|
||||
list.sort_by_key(|a| a.weight());
|
||||
}
|
||||
|
||||
pub fn iter_map<B, F>(&self, f: F)
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(&Action) -> B,
|
||||
{
|
||||
let _: Vec<_> = self.0.read().unwrap().iter().map(f).collect();
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! actions {
|
||||
() => {
|
||||
Vec::<Action>::new()
|
||||
};
|
||||
( $($action:expr),+ $(,)? ) => {{
|
||||
let mut v = Vec::<Action>::new();
|
||||
$(
|
||||
v.push(Box::new($action));
|
||||
)*
|
||||
v
|
||||
}};
|
||||
}
|
||||
20
src/core/component.rs
Normal file
20
src/core/component.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
mod context;
|
||||
pub use context::{Context, ContextOp};
|
||||
pub type FnContextualPath = fn(cx: &Context) -> &str;
|
||||
|
||||
mod renderable;
|
||||
pub use renderable::{FnIsRenderable, Renderable};
|
||||
|
||||
mod definition;
|
||||
pub use definition::{component_as_mut, component_as_ref, ComponentBase, ComponentTrait};
|
||||
|
||||
mod classes;
|
||||
pub use classes::{ImplementClasses, ImplementClassesOp};
|
||||
|
||||
mod arc_any;
|
||||
pub use arc_any::AnyComponents;
|
||||
pub use arc_any::{ArcAnyComponent, ArcAnyOp};
|
||||
|
||||
mod arc_typed;
|
||||
pub use arc_typed::TypedComponents;
|
||||
pub use arc_typed::{ArcTypedComponent, ArcTypedOp};
|
||||
144
src/core/component/arc_any.rs
Normal file
144
src/core/component/arc_any.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
use crate::core::component::{ComponentTrait, Context};
|
||||
use crate::html::{html, Markup};
|
||||
use crate::{fn_with, Handle, Weight};
|
||||
|
||||
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ArcAnyComponent(Arc<RwLock<dyn ComponentTrait>>);
|
||||
|
||||
impl ArcAnyComponent {
|
||||
pub fn new(component: impl ComponentTrait) -> Self {
|
||||
ArcAnyComponent(Arc::new(RwLock::new(component)))
|
||||
}
|
||||
|
||||
// ArcAnyComponent BUILDER.
|
||||
|
||||
pub fn set(&mut self, component: impl ComponentTrait) {
|
||||
self.0 = Arc::new(RwLock::new(component));
|
||||
}
|
||||
|
||||
// ArcAnyComponent GETTERS.
|
||||
|
||||
pub fn get(&self) -> RwLockReadGuard<'_, dyn ComponentTrait> {
|
||||
self.0.read().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_mut(&self) -> RwLockWriteGuard<'_, dyn ComponentTrait> {
|
||||
self.0.write().unwrap()
|
||||
}
|
||||
|
||||
// ArcAnyComponent RENDER.
|
||||
|
||||
pub fn render(&self, cx: &mut Context) -> Markup {
|
||||
self.0.write().unwrap().render(cx)
|
||||
}
|
||||
|
||||
// ArcAnyComponent HELPERS.
|
||||
|
||||
fn handle(&self) -> Handle {
|
||||
self.0.read().unwrap().handle()
|
||||
}
|
||||
|
||||
fn id(&self) -> String {
|
||||
self.0.read().unwrap().id().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.0.read().unwrap().weight()
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
pub enum ArcAnyOp {
|
||||
Add(ArcAnyComponent),
|
||||
AddAfterId(&'static str, ArcAnyComponent),
|
||||
AddBeforeId(&'static str, ArcAnyComponent),
|
||||
Prepend(ArcAnyComponent),
|
||||
RemoveById(&'static str),
|
||||
ReplaceById(&'static str, ArcAnyComponent),
|
||||
Reset,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct AnyComponents(Vec<ArcAnyComponent>);
|
||||
|
||||
impl AnyComponents {
|
||||
pub fn new(arc: ArcAnyComponent) -> Self {
|
||||
AnyComponents::default().with_value(ArcAnyOp::Add(arc))
|
||||
}
|
||||
|
||||
pub(crate) fn merge(mixes: &[Option<&AnyComponents>]) -> Self {
|
||||
let mut opt = AnyComponents::default();
|
||||
for m in mixes.iter().flatten() {
|
||||
opt.0.append(&mut m.0.clone());
|
||||
}
|
||||
opt
|
||||
}
|
||||
|
||||
// AnyComponents BUILDER.
|
||||
|
||||
#[fn_with]
|
||||
pub fn alter_value(&mut self, op: ArcAnyOp) -> &mut Self {
|
||||
match op {
|
||||
ArcAnyOp::Add(arc) => self.0.push(arc),
|
||||
ArcAnyOp::AddAfterId(id, arc) => match self.0.iter().position(|c| c.id() == id) {
|
||||
Some(index) => self.0.insert(index + 1, arc),
|
||||
_ => self.0.push(arc),
|
||||
},
|
||||
ArcAnyOp::AddBeforeId(id, arc) => match self.0.iter().position(|c| c.id() == id) {
|
||||
Some(index) => self.0.insert(index, arc),
|
||||
_ => self.0.insert(0, arc),
|
||||
},
|
||||
ArcAnyOp::Prepend(arc) => self.0.insert(0, arc),
|
||||
ArcAnyOp::RemoveById(id) => {
|
||||
if let Some(index) = self.0.iter().position(|c| c.id() == id) {
|
||||
self.0.remove(index);
|
||||
}
|
||||
}
|
||||
ArcAnyOp::ReplaceById(id, arc) => {
|
||||
for c in self.0.iter_mut() {
|
||||
if c.id() == id {
|
||||
*c = arc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ArcAnyOp::Reset => self.0.clear(),
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// AnyComponents GETTERS.
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_by_id(&self, id: impl Into<String>) -> Option<&ArcAnyComponent> {
|
||||
let id = id.into();
|
||||
self.0.iter().find(|c| c.id() == id)
|
||||
}
|
||||
|
||||
pub fn iter_by_id(&self, id: impl Into<String>) -> impl Iterator<Item = &ArcAnyComponent> {
|
||||
let id = id.into();
|
||||
self.0.iter().filter(move |&c| c.id() == id)
|
||||
}
|
||||
|
||||
pub fn iter_by_handle(&self, handle: Handle) -> impl Iterator<Item = &ArcAnyComponent> {
|
||||
self.0.iter().filter(move |&c| c.handle() == handle)
|
||||
}
|
||||
|
||||
// AnyComponents RENDER.
|
||||
|
||||
pub fn render(&self, cx: &mut Context) -> Markup {
|
||||
let mut components = self.0.clone();
|
||||
components.sort_by_key(|c| c.weight());
|
||||
html! {
|
||||
@for c in components.iter() {
|
||||
" " (c.render(cx)) " "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
src/core/component/arc_typed.rs
Normal file
141
src/core/component/arc_typed.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
use crate::core::component::{ComponentTrait, Context};
|
||||
use crate::html::{html, Markup};
|
||||
use crate::{fn_with, Handle, Weight};
|
||||
|
||||
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
pub struct ArcTypedComponent<C: ComponentTrait>(Arc<RwLock<C>>);
|
||||
|
||||
impl<C: ComponentTrait> Clone for ArcTypedComponent<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> ArcTypedComponent<C> {
|
||||
pub fn new(component: C) -> Self {
|
||||
ArcTypedComponent(Arc::new(RwLock::new(component)))
|
||||
}
|
||||
|
||||
// ArcTypedComponent BUILDER.
|
||||
|
||||
pub fn set(&mut self, component: C) {
|
||||
self.0 = Arc::new(RwLock::new(component));
|
||||
}
|
||||
|
||||
// ArcTypedComponent GETTERS.
|
||||
|
||||
pub fn get(&self) -> RwLockReadGuard<'_, C> {
|
||||
self.0.read().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_mut(&self) -> RwLockWriteGuard<'_, C> {
|
||||
self.0.write().unwrap()
|
||||
}
|
||||
|
||||
// ArcTypedComponent RENDER.
|
||||
|
||||
pub fn render(&self, cx: &mut Context) -> Markup {
|
||||
self.0.write().unwrap().render(cx)
|
||||
}
|
||||
|
||||
// ArcTypedComponent HELPERS.
|
||||
|
||||
fn handle(&self) -> Handle {
|
||||
self.0.read().unwrap().handle()
|
||||
}
|
||||
|
||||
fn id(&self) -> String {
|
||||
self.0.read().unwrap().id().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.0.read().unwrap().weight()
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
pub enum ArcTypedOp<C: ComponentTrait> {
|
||||
Add(ArcTypedComponent<C>),
|
||||
AddAfterId(&'static str, ArcTypedComponent<C>),
|
||||
AddBeforeId(&'static str, ArcTypedComponent<C>),
|
||||
Prepend(ArcTypedComponent<C>),
|
||||
RemoveById(&'static str),
|
||||
ReplaceById(&'static str, ArcTypedComponent<C>),
|
||||
Reset,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct TypedComponents<C: ComponentTrait>(Vec<ArcTypedComponent<C>>);
|
||||
|
||||
impl<C: ComponentTrait + Default> TypedComponents<C> {
|
||||
pub fn new(arc: ArcTypedComponent<C>) -> Self {
|
||||
TypedComponents::default().with_value(ArcTypedOp::Add(arc))
|
||||
}
|
||||
|
||||
// TypedComponents BUILDER.
|
||||
|
||||
#[fn_with]
|
||||
pub fn alter_value(&mut self, op: ArcTypedOp<C>) -> &mut Self {
|
||||
match op {
|
||||
ArcTypedOp::Add(one) => self.0.push(one),
|
||||
ArcTypedOp::AddAfterId(id, one) => match self.0.iter().position(|c| c.id() == id) {
|
||||
Some(index) => self.0.insert(index + 1, one),
|
||||
_ => self.0.push(one),
|
||||
},
|
||||
ArcTypedOp::AddBeforeId(id, one) => match self.0.iter().position(|c| c.id() == id) {
|
||||
Some(index) => self.0.insert(index, one),
|
||||
_ => self.0.insert(0, one),
|
||||
},
|
||||
ArcTypedOp::Prepend(one) => self.0.insert(0, one),
|
||||
ArcTypedOp::RemoveById(id) => {
|
||||
if let Some(index) = self.0.iter().position(|c| c.id() == id) {
|
||||
self.0.remove(index);
|
||||
}
|
||||
}
|
||||
ArcTypedOp::ReplaceById(id, one) => {
|
||||
for c in self.0.iter_mut() {
|
||||
if c.id() == id {
|
||||
*c = one;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ArcTypedOp::Reset => self.0.clear(),
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// TypedComponents GETTERS.
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_by_id(&self, id: impl Into<String>) -> Option<&ArcTypedComponent<C>> {
|
||||
let id = id.into();
|
||||
self.0.iter().find(|&c| c.id() == id)
|
||||
}
|
||||
|
||||
pub fn iter_by_id(&self, id: impl Into<String>) -> impl Iterator<Item = &ArcTypedComponent<C>> {
|
||||
let id = id.into();
|
||||
self.0.iter().filter(move |&c| c.id() == id)
|
||||
}
|
||||
|
||||
pub fn iter_by_handle(&self, handle: Handle) -> impl Iterator<Item = &ArcTypedComponent<C>> {
|
||||
self.0.iter().filter(move |&c| c.handle() == handle)
|
||||
}
|
||||
|
||||
// TypedComponents RENDER.
|
||||
|
||||
pub fn render(&self, cx: &mut Context) -> Markup {
|
||||
let mut components = self.0.clone();
|
||||
components.sort_by_key(|c| c.weight());
|
||||
html! {
|
||||
@for c in components.iter() {
|
||||
" " (c.render(cx)) " "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/core/component/classes.rs
Normal file
60
src/core/component/classes.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use crate::html::{ClassesOp, OptionClasses};
|
||||
|
||||
pub trait ImplementClassesOp {
|
||||
fn with_classes(self, op: ClassesOp, classes: impl Into<String>) -> Self;
|
||||
|
||||
fn add_classes(&mut self, classes: impl Into<String>) -> &mut Self;
|
||||
|
||||
fn prepend_classes(&mut self, classes: impl Into<String>) -> &mut Self;
|
||||
|
||||
fn remove_classes(&mut self, classes: impl Into<String>) -> &mut Self;
|
||||
|
||||
fn replace_classes(&mut self, rep: impl Into<String>, classes: impl Into<String>) -> &mut Self;
|
||||
|
||||
fn toggle_classes(&mut self, classes: impl Into<String>) -> &mut Self;
|
||||
|
||||
fn set_classes(&mut self, classes: impl Into<String>) -> &mut Self;
|
||||
}
|
||||
|
||||
pub trait ImplementClasses: ImplementClassesOp {
|
||||
fn alter_classes(&mut self, op: ClassesOp, classes: impl Into<String>) -> &mut Self;
|
||||
|
||||
fn classes(&self) -> &OptionClasses;
|
||||
}
|
||||
|
||||
impl<C: ImplementClasses> ImplementClassesOp for C {
|
||||
fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
|
||||
self.alter_classes(op, classes);
|
||||
self
|
||||
}
|
||||
|
||||
fn add_classes(&mut self, classes: impl Into<String>) -> &mut Self {
|
||||
self.alter_classes(ClassesOp::Add, classes);
|
||||
self
|
||||
}
|
||||
|
||||
fn prepend_classes(&mut self, classes: impl Into<String>) -> &mut Self {
|
||||
self.alter_classes(ClassesOp::Prepend, classes);
|
||||
self
|
||||
}
|
||||
|
||||
fn remove_classes(&mut self, classes: impl Into<String>) -> &mut Self {
|
||||
self.alter_classes(ClassesOp::Remove, classes);
|
||||
self
|
||||
}
|
||||
|
||||
fn replace_classes(&mut self, rep: impl Into<String>, classes: impl Into<String>) -> &mut Self {
|
||||
self.alter_classes(ClassesOp::Replace(rep.into()), classes);
|
||||
self
|
||||
}
|
||||
|
||||
fn toggle_classes(&mut self, classes: impl Into<String>) -> &mut Self {
|
||||
self.alter_classes(ClassesOp::Toggle, classes);
|
||||
self
|
||||
}
|
||||
|
||||
fn set_classes(&mut self, classes: impl Into<String>) -> &mut Self {
|
||||
self.alter_classes(ClassesOp::Set, classes);
|
||||
self
|
||||
}
|
||||
}
|
||||
153
src/core/component/context.rs
Normal file
153
src/core/component/context.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
use crate::base::component::add_base_assets;
|
||||
use crate::core::theme::all::{theme_by_single_name, THEME};
|
||||
use crate::core::theme::ThemeRef;
|
||||
use crate::html::{html, Assets, HeadScript, HeadStyles, JavaScript, Markup, StyleSheet};
|
||||
use crate::locale::{LanguageIdentifier, LANGID};
|
||||
use crate::service::HttpRequest;
|
||||
use crate::{concat_string, util};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub enum ContextOp {
|
||||
LangId(&'static LanguageIdentifier),
|
||||
Theme(&'static str),
|
||||
// Stylesheets.
|
||||
AddStyleSheet(StyleSheet),
|
||||
RemoveStyleSheet(&'static str),
|
||||
// Styles in head.
|
||||
AddHeadStyles(HeadStyles),
|
||||
RemoveHeadStyles(&'static str),
|
||||
// JavaScripts.
|
||||
AddJavaScript(JavaScript),
|
||||
RemoveJavaScript(&'static str),
|
||||
// Scripts in head.
|
||||
AddHeadScript(HeadScript),
|
||||
RemoveHeadScript(&'static str),
|
||||
// Add assets to properly use base components.
|
||||
AddBaseAssets,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub struct Context {
|
||||
request : HttpRequest,
|
||||
langid : &'static LanguageIdentifier,
|
||||
theme : ThemeRef,
|
||||
stylesheet: Assets<StyleSheet>, // Stylesheets.
|
||||
headstyles: Assets<HeadStyles>, // Styles in head.
|
||||
javascript: Assets<JavaScript>, // JavaScripts.
|
||||
headscript: Assets<HeadScript>, // Scripts in head.
|
||||
params : HashMap<&'static str, String>,
|
||||
id_counter: usize,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
#[rustfmt::skip]
|
||||
pub(crate) fn new(request: HttpRequest) -> Self {
|
||||
Context {
|
||||
request,
|
||||
langid : &LANGID,
|
||||
theme : *THEME,
|
||||
stylesheet: Assets::<StyleSheet>::new(), // Stylesheets.
|
||||
headstyles: Assets::<HeadStyles>::new(), // Styles in head.
|
||||
javascript: Assets::<JavaScript>::new(), // JavaScripts.
|
||||
headscript: Assets::<HeadScript>::new(), // Scripts in head.
|
||||
params : HashMap::<&str, String>::new(),
|
||||
id_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn alter(&mut self, op: ContextOp) -> &mut Self {
|
||||
match op {
|
||||
ContextOp::LangId(langid) => {
|
||||
self.langid = langid;
|
||||
}
|
||||
ContextOp::Theme(theme_name) => {
|
||||
self.theme = theme_by_single_name(theme_name).unwrap_or(*THEME);
|
||||
}
|
||||
|
||||
// Stylesheets.
|
||||
ContextOp::AddStyleSheet(css) => { self.stylesheet.add(css); }
|
||||
ContextOp::RemoveStyleSheet(path) => { self.stylesheet.remove(path); }
|
||||
// Styles in head.
|
||||
ContextOp::AddHeadStyles(styles) => { self.headstyles.add(styles); }
|
||||
ContextOp::RemoveHeadStyles(path) => { self.headstyles.remove(path); }
|
||||
// JavaScripts.
|
||||
ContextOp::AddJavaScript(js) => { self.javascript.add(js); }
|
||||
ContextOp::RemoveJavaScript(path) => { self.javascript.remove(path); }
|
||||
// Scripts in head.
|
||||
ContextOp::AddHeadScript(script) => { self.headscript.add(script); }
|
||||
ContextOp::RemoveHeadScript(path) => { self.headscript.remove(path); }
|
||||
|
||||
// Add assets to properly use base components.
|
||||
ContextOp::AddBaseAssets => { add_base_assets(self); }
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_param<T: FromStr + ToString>(&mut self, key: &'static str, value: T) -> &mut Self {
|
||||
self.params.insert(key, value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remove_param(&mut self, key: &'static str) -> &mut Self {
|
||||
self.params.remove(key);
|
||||
self
|
||||
}
|
||||
|
||||
/// Context GETTERS.
|
||||
|
||||
pub fn request(&self) -> &HttpRequest {
|
||||
&self.request
|
||||
}
|
||||
|
||||
pub fn langid(&self) -> &LanguageIdentifier {
|
||||
self.langid
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> ThemeRef {
|
||||
self.theme
|
||||
}
|
||||
|
||||
pub fn get_param<T: FromStr + ToString>(&mut self, key: &'static str) -> Option<T> {
|
||||
if let Some(value) = self.params.get(key) {
|
||||
if let Ok(value) = T::from_str(value) {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Context PREPARE.
|
||||
|
||||
pub fn prepare(&mut self) -> Markup {
|
||||
html! {
|
||||
(self.stylesheet.prepare()) // Stylesheets.
|
||||
(self.headstyles.prepare()) // Styles in head.
|
||||
(self.javascript.prepare()) // JavaScripts.
|
||||
(self.headscript.prepare()) // Scripts in head.
|
||||
}
|
||||
}
|
||||
|
||||
// Context EXTRAS.
|
||||
|
||||
pub fn required_id<T>(&mut self, id: Option<String>) -> String {
|
||||
match id {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
let prefix = util::single_type_name::<T>()
|
||||
.trim()
|
||||
.replace(' ', "_")
|
||||
.to_lowercase();
|
||||
let prefix = if prefix.is_empty() {
|
||||
"prefix".to_owned()
|
||||
} else {
|
||||
prefix
|
||||
};
|
||||
self.id_counter += 1;
|
||||
concat_string!(prefix, "-", self.id_counter.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/core/component/definition.rs
Normal file
106
src/core/component/definition.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
use crate::base::action;
|
||||
use crate::core::component::Context;
|
||||
use crate::html::{html, Markup, PrepareMarkup};
|
||||
use crate::{util, ImplementHandle, Weight};
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
pub trait ComponentBase: Any {
|
||||
fn render(&mut self, cx: &mut Context) -> Markup;
|
||||
|
||||
fn as_ref_any(&self) -> &dyn Any;
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
pub trait ComponentTrait: ComponentBase + ImplementHandle + Send + Sync {
|
||||
fn new() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn name(&self) -> String {
|
||||
util::single_type_name::<Self>().to_owned()
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
0
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn is_renderable(&self, cx: &Context) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn setup_before_prepare(&mut self, cx: &mut Context) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::None
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> ComponentBase for C {
|
||||
fn render(&mut self, cx: &mut Context) -> Markup {
|
||||
if self.is_renderable(cx) {
|
||||
// Comprueba el componente antes de prepararlo.
|
||||
self.setup_before_prepare(cx);
|
||||
|
||||
// Acciones del tema antes de preparar el componente.
|
||||
cx.theme().before_prepare_component(self, cx);
|
||||
|
||||
// Acciones de los módulos antes de preparar el componente.
|
||||
action::component::BeforePrepareComponent::dispatch(self, cx, None);
|
||||
if let Some(id) = self.id() {
|
||||
action::component::BeforePrepareComponent::dispatch(self, cx, Some(id));
|
||||
}
|
||||
|
||||
// Renderiza el componente.
|
||||
let markup = match cx.theme().render_component(self, cx) {
|
||||
Some(html) => html,
|
||||
None => match self.prepare_component(cx) {
|
||||
PrepareMarkup::None => html! {},
|
||||
PrepareMarkup::Text(text) => html! { (text) },
|
||||
PrepareMarkup::With(html) => html,
|
||||
},
|
||||
};
|
||||
|
||||
// Acciones del tema después de preparar el componente.
|
||||
cx.theme().after_prepare_component(self, cx);
|
||||
|
||||
// Acciones de los módulos después de preparar el componente.
|
||||
action::component::AfterPrepareComponent::dispatch(self, cx, None);
|
||||
if let Some(id) = self.id() {
|
||||
action::component::AfterPrepareComponent::dispatch(self, cx, Some(id));
|
||||
}
|
||||
|
||||
markup
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn component_as_ref<C: ComponentTrait>(component: &dyn ComponentTrait) -> Option<&C> {
|
||||
component.as_ref_any().downcast_ref::<C>()
|
||||
}
|
||||
|
||||
pub fn component_as_mut<C: ComponentTrait>(component: &mut dyn ComponentTrait) -> Option<&mut C> {
|
||||
component.as_mut_any().downcast_mut::<C>()
|
||||
}
|
||||
14
src/core/component/renderable.rs
Normal file
14
src/core/component/renderable.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use crate::core::component::Context;
|
||||
use crate::SmartDefault;
|
||||
|
||||
pub type FnIsRenderable = fn(cx: &Context) -> bool;
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct Renderable {
|
||||
#[default(_code = "render_always")]
|
||||
pub check: FnIsRenderable,
|
||||
}
|
||||
|
||||
fn render_always(_cx: &Context) -> bool {
|
||||
true
|
||||
}
|
||||
4
src/core/package.rs
Normal file
4
src/core/package.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
mod definition;
|
||||
pub use definition::{PackageBase, PackageRef, PackageTrait};
|
||||
|
||||
pub(crate) mod all;
|
||||
162
src/core/package/all.rs
Normal file
162
src/core/package/all.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use crate::core::action::add_action;
|
||||
use crate::core::package::PackageRef;
|
||||
use crate::core::theme::all::THEMES;
|
||||
use crate::{config, service, service_for_static_files, static_files, trace, LazyStatic};
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
use crate::db::*;
|
||||
|
||||
use std::sync::RwLock;
|
||||
|
||||
static_files!(base);
|
||||
|
||||
// PACKAGES ****************************************************************************************
|
||||
|
||||
static ENABLED_PACKAGES: LazyStatic<RwLock<Vec<PackageRef>>> =
|
||||
LazyStatic::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
static DROPPED_PACKAGES: LazyStatic<RwLock<Vec<PackageRef>>> =
|
||||
LazyStatic::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
// REGISTER PACKAGES *******************************************************************************
|
||||
|
||||
pub fn register_packages(app: PackageRef) {
|
||||
// List of packages to drop.
|
||||
let mut list: Vec<PackageRef> = Vec::new();
|
||||
add_to_dropped(&mut list, app);
|
||||
DROPPED_PACKAGES.write().unwrap().append(&mut list);
|
||||
|
||||
// List of packages to enable.
|
||||
let mut list: Vec<PackageRef> = Vec::new();
|
||||
|
||||
// Enable default themes.
|
||||
add_to_enabled(&mut list, &crate::base::theme::Basic);
|
||||
add_to_enabled(&mut list, &crate::base::theme::Chassis);
|
||||
add_to_enabled(&mut list, &crate::base::theme::Inception);
|
||||
|
||||
// Enable application packages.
|
||||
add_to_enabled(&mut list, app);
|
||||
|
||||
list.reverse();
|
||||
ENABLED_PACKAGES.write().unwrap().append(&mut list);
|
||||
}
|
||||
|
||||
fn add_to_dropped(list: &mut Vec<PackageRef>, package: PackageRef) {
|
||||
for d in package.drop_packages().iter() {
|
||||
if !list.iter().any(|p| p.handle() == d.handle()) {
|
||||
list.push(*d);
|
||||
trace::debug!("Package \"{}\" dropped", d.single_name());
|
||||
}
|
||||
}
|
||||
for d in package.dependencies().iter() {
|
||||
add_to_dropped(list, *d);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to_enabled(list: &mut Vec<PackageRef>, package: PackageRef) {
|
||||
if !list.iter().any(|p| p.handle() == package.handle()) {
|
||||
if DROPPED_PACKAGES
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|p| p.handle() == package.handle())
|
||||
{
|
||||
panic!(
|
||||
"Trying to enable \"{}\" package which is dropped",
|
||||
package.single_name()
|
||||
);
|
||||
} else {
|
||||
list.push(package);
|
||||
|
||||
let mut dependencies = package.dependencies();
|
||||
dependencies.reverse();
|
||||
for d in dependencies.iter() {
|
||||
add_to_enabled(list, *d);
|
||||
}
|
||||
|
||||
if let Some(theme) = package.theme() {
|
||||
let mut registered_themes = THEMES.write().unwrap();
|
||||
if !registered_themes
|
||||
.iter()
|
||||
.any(|t| t.handle() == theme.handle())
|
||||
{
|
||||
registered_themes.push(theme);
|
||||
trace::debug!("Enabling \"{}\" theme", theme.single_name());
|
||||
}
|
||||
} else {
|
||||
trace::debug!("Enabling \"{}\" package", package.single_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// REGISTER ACTIONS ********************************************************************************
|
||||
|
||||
pub fn register_actions() {
|
||||
for m in ENABLED_PACKAGES.read().unwrap().iter() {
|
||||
for a in m.actions().into_iter() {
|
||||
add_action(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// INIT PACKAGES ***********************************************************************************
|
||||
|
||||
pub fn init_packages() {
|
||||
trace::info!("Calling application bootstrap");
|
||||
for m in ENABLED_PACKAGES.read().unwrap().iter() {
|
||||
m.init();
|
||||
}
|
||||
}
|
||||
|
||||
// RUN MIGRATIONS **********************************************************************************
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
pub fn run_migrations() {
|
||||
if let Some(dbconn) = &*DBCONN {
|
||||
if let Err(e) = run_now({
|
||||
struct Migrator;
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<MigrationItem> {
|
||||
let mut migrations = vec![];
|
||||
for m in ENABLED_PACKAGES.read().unwrap().iter() {
|
||||
migrations.append(&mut m.migrations());
|
||||
}
|
||||
migrations
|
||||
}
|
||||
}
|
||||
Migrator::up(SchemaManagerConnection::Connection(dbconn), None)
|
||||
}) {
|
||||
trace::error!("Database upgrade failed ({})", e);
|
||||
};
|
||||
|
||||
if let Err(e) = run_now({
|
||||
struct Migrator;
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<MigrationItem> {
|
||||
let mut migrations = vec![];
|
||||
for m in DROPPED_PACKAGES.read().unwrap().iter() {
|
||||
migrations.append(&mut m.migrations());
|
||||
}
|
||||
migrations
|
||||
}
|
||||
}
|
||||
Migrator::down(SchemaManagerConnection::Connection(dbconn), None)
|
||||
}) {
|
||||
trace::error!("Database downgrade failed ({})", e);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// CONFIGURE SERVICES ******************************************************************************
|
||||
|
||||
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
|
||||
service_for_static_files!(
|
||||
scfg,
|
||||
base => "/base",
|
||||
[&config::SETTINGS.dev.pagetop_project_dir, "static/base"]
|
||||
);
|
||||
for m in ENABLED_PACKAGES.read().unwrap().iter() {
|
||||
m.configure_service(scfg);
|
||||
}
|
||||
}
|
||||
57
src/core/package/definition.rs
Normal file
57
src/core/package/definition.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use crate::core::action::Action;
|
||||
use crate::core::theme::ThemeRef;
|
||||
use crate::locale::L10n;
|
||||
use crate::{actions, service, util, ImplementHandle};
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
use crate::{db::MigrationItem, migrations};
|
||||
|
||||
pub type PackageRef = &'static dyn PackageTrait;
|
||||
|
||||
pub trait PackageBase {
|
||||
fn single_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
/// Los paquetes deben implementar este *trait*.
|
||||
pub trait PackageTrait: ImplementHandle + PackageBase + Send + Sync {
|
||||
fn name(&self) -> L10n {
|
||||
L10n::n(self.single_name())
|
||||
}
|
||||
|
||||
fn description(&self) -> L10n {
|
||||
L10n::none()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Option<ThemeRef> {
|
||||
None
|
||||
}
|
||||
|
||||
fn dependencies(&self) -> Vec<PackageRef> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn drop_packages(&self) -> Vec<PackageRef> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn actions(&self) -> Vec<Action> {
|
||||
actions![]
|
||||
}
|
||||
|
||||
fn init(&self) {}
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
#[allow(unused_variables)]
|
||||
fn migrations(&self) -> Vec<MigrationItem> {
|
||||
migrations![]
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {}
|
||||
}
|
||||
|
||||
impl<M: ?Sized + PackageTrait> PackageBase for M {
|
||||
fn single_name(&self) -> &'static str {
|
||||
util::single_type_name::<Self>()
|
||||
}
|
||||
}
|
||||
8
src/core/theme.rs
Normal file
8
src/core/theme.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
mod definition;
|
||||
pub use definition::{ThemeRef, ThemeTrait};
|
||||
|
||||
mod regions;
|
||||
pub(crate) use regions::ComponentsInRegions;
|
||||
pub use regions::{add_component_in, Region};
|
||||
|
||||
pub(crate) mod all;
|
||||
32
src/core/theme/all.rs
Normal file
32
src/core/theme/all.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use crate::config;
|
||||
use crate::core::theme::ThemeRef;
|
||||
use crate::LazyStatic;
|
||||
|
||||
use std::sync::RwLock;
|
||||
|
||||
// THEMES ******************************************************************************************
|
||||
|
||||
pub static THEMES: LazyStatic<RwLock<Vec<ThemeRef>>> = LazyStatic::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
// DEFAULT THEME ***********************************************************************************
|
||||
|
||||
pub static THEME: LazyStatic<ThemeRef> =
|
||||
LazyStatic::new(|| match theme_by_single_name(&config::SETTINGS.app.theme) {
|
||||
Some(theme) => theme,
|
||||
None => &crate::base::theme::Inception,
|
||||
});
|
||||
|
||||
// THEME BY NAME ***********************************************************************************
|
||||
|
||||
pub fn theme_by_single_name(single_name: &str) -> Option<ThemeRef> {
|
||||
let single_name = single_name.to_lowercase();
|
||||
match THEMES
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|t| t.single_name().to_lowercase() == single_name)
|
||||
{
|
||||
Some(theme) => Some(*theme),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
174
src/core/theme/definition.rs
Normal file
174
src/core/theme/definition.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
use crate::core::component::{ComponentTrait, Context};
|
||||
use crate::core::package::PackageTrait;
|
||||
use crate::html::{html, Favicon, Markup, OptionId};
|
||||
use crate::locale::L10n;
|
||||
use crate::response::page::Page;
|
||||
use crate::{concat_string, config};
|
||||
|
||||
pub type ThemeRef = &'static dyn ThemeTrait;
|
||||
|
||||
/// Los temas deben implementar este "trait".
|
||||
pub trait ThemeTrait: PackageTrait + Send + Sync {
|
||||
#[rustfmt::skip]
|
||||
fn regions(&self) -> Vec<(&'static str, L10n)> {
|
||||
vec![
|
||||
("header", L10n::l("header")),
|
||||
("pagetop", L10n::l("pagetop")),
|
||||
("content", L10n::l("content")),
|
||||
("sidebar", L10n::l("sidebar")),
|
||||
("footer", L10n::l("footer")),
|
||||
]
|
||||
}
|
||||
|
||||
fn prepare_region(&self, page: &mut Page, region: &str) -> Markup {
|
||||
let render_region = page.components_in(region).render(page.context());
|
||||
if render_region.is_empty() {
|
||||
html! {}
|
||||
} else {
|
||||
let id = OptionId::new(region).get().unwrap();
|
||||
let id_inner = concat_string!(id, "__inner");
|
||||
html! {
|
||||
div id=(id) class="pt-region" {
|
||||
div id=(id_inner) class="pt-region__inner" {
|
||||
(render_region)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn before_prepare_body(&self, page: &mut Page) {}
|
||||
|
||||
fn prepare_body(&self, page: &mut Page) -> Markup {
|
||||
let skip_to = concat_string!("#", page.skip_to().get().unwrap_or("content".to_owned()));
|
||||
|
||||
html! {
|
||||
body class=[page.body_classes().get()] {
|
||||
@if let Some(skip) = L10n::l("skip_to_content").using(page.context().langid()) {
|
||||
div class="pt-body__skip" {
|
||||
a href=(skip_to) { (skip) }
|
||||
}
|
||||
}
|
||||
div class="pt-body__wrapper" {
|
||||
div class="pt-body__regions" {
|
||||
(self.prepare_region(page, "header"))
|
||||
(self.prepare_region(page, "pagetop"))
|
||||
div class="pt-content" {
|
||||
div class="pt-content__wrapper" {
|
||||
(self.prepare_region(page, "content"))
|
||||
(self.prepare_region(page, "sidebar"))
|
||||
}
|
||||
}
|
||||
(self.prepare_region(page, "footer"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn after_prepare_body(&self, page: &mut Page) {
|
||||
if page.favicon().is_none() {
|
||||
page.alter_favicon(Some(Favicon::new().with_icon("/base/favicon.ico")));
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_head(&self, page: &mut Page) -> Markup {
|
||||
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
|
||||
html! {
|
||||
head {
|
||||
meta charset="utf-8";
|
||||
|
||||
@if let Some(title) = page.title() {
|
||||
title { (config::SETTINGS.app.name) (" - ") (title) }
|
||||
} @else {
|
||||
title { (config::SETTINGS.app.name) }
|
||||
}
|
||||
|
||||
@if let Some(description) = page.description() {
|
||||
meta name="description" content=(description);
|
||||
}
|
||||
|
||||
meta name="viewport" content=(viewport);
|
||||
@for (name, content) in page.metadata() {
|
||||
meta name=(name) content=(content) {}
|
||||
}
|
||||
|
||||
meta http-equiv="X-UA-Compatible" content="IE=edge";
|
||||
@for (property, content) in page.properties() {
|
||||
meta property=(property) content=(content) {}
|
||||
}
|
||||
|
||||
@if let Some(favicon) = page.favicon() {
|
||||
(favicon.prepare())
|
||||
}
|
||||
|
||||
(page.context().prepare())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(unused_variables)]
|
||||
fn before_prepare_component(
|
||||
&self,
|
||||
component: &mut dyn ComponentTrait,
|
||||
cx: &mut Context,
|
||||
) {
|
||||
/*
|
||||
Cómo usarlo:
|
||||
|
||||
match component.handle() {
|
||||
BLOCK_COMPONENT => {
|
||||
let block = component_as_mut::<Block>(component);
|
||||
block.alter_title("New title");
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(unused_variables)]
|
||||
fn after_prepare_component(
|
||||
&self,
|
||||
component: &mut dyn ComponentTrait,
|
||||
cx: &mut Context,
|
||||
) {
|
||||
/*
|
||||
Cómo usarlo:
|
||||
|
||||
match component.handle() {
|
||||
BLOCK_COMPONENT => {
|
||||
let block = component_as_mut::<Block>(component);
|
||||
block.alter_title("New title");
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(unused_variables)]
|
||||
fn render_component(
|
||||
&self,
|
||||
component: &dyn ComponentTrait,
|
||||
cx: &mut Context,
|
||||
) -> Option<Markup> {
|
||||
None
|
||||
/*
|
||||
Cómo usarlo:
|
||||
|
||||
match component.handle() {
|
||||
BLOCK_COMPONENT => {
|
||||
let block = component_as_ref::<Block>(component);
|
||||
match block.template() {
|
||||
"default" => Some(block_default(block)),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
61
src/core/theme/regions.rs
Normal file
61
src/core/theme/regions.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use crate::core::component::{AnyComponents, ArcAnyComponent, ArcAnyOp};
|
||||
use crate::core::theme::ThemeRef;
|
||||
use crate::{Handle, LazyStatic, SmartDefault};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
|
||||
static THEME_REGIONS: LazyStatic<RwLock<HashMap<Handle, ComponentsInRegions>>> =
|
||||
LazyStatic::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
static COMMON_REGIONS: LazyStatic<RwLock<ComponentsInRegions>> =
|
||||
LazyStatic::new(|| RwLock::new(ComponentsInRegions::default()));
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct ComponentsInRegions(HashMap<&'static str, AnyComponents>);
|
||||
|
||||
impl ComponentsInRegions {
|
||||
pub fn new(region: &'static str, arc: ArcAnyComponent) -> Self {
|
||||
let mut regions = ComponentsInRegions::default();
|
||||
regions.add_component_in(region, arc);
|
||||
regions
|
||||
}
|
||||
|
||||
pub fn add_component_in(&mut self, region: &'static str, arc: ArcAnyComponent) {
|
||||
if let Some(region) = self.0.get_mut(region) {
|
||||
region.alter_value(ArcAnyOp::Add(arc));
|
||||
} else {
|
||||
self.0.insert(region, AnyComponents::new(arc));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_components(&self, theme: ThemeRef, region: &str) -> AnyComponents {
|
||||
let common = COMMON_REGIONS.read().unwrap();
|
||||
if let Some(r) = THEME_REGIONS.read().unwrap().get(&theme.handle()) {
|
||||
AnyComponents::merge(&[common.0.get(region), self.0.get(region), r.0.get(region)])
|
||||
} else {
|
||||
AnyComponents::merge(&[common.0.get(region), self.0.get(region)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Region {
|
||||
Named(&'static str),
|
||||
OfTheme(ThemeRef, &'static str),
|
||||
}
|
||||
|
||||
pub fn add_component_in(region: Region, arc: ArcAnyComponent) {
|
||||
match region {
|
||||
Region::Named(name) => {
|
||||
COMMON_REGIONS.write().unwrap().add_component_in(name, arc);
|
||||
}
|
||||
Region::OfTheme(theme, region) => {
|
||||
let mut regions = THEME_REGIONS.write().unwrap();
|
||||
if let Some(r) = regions.get_mut(&theme.handle()) {
|
||||
r.add_component_in(region, arc);
|
||||
} else {
|
||||
regions.insert(theme.handle(), ComponentsInRegions::new(region, arc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue