diff --git a/src/Layout.ts b/src/Layout.ts index 26666b1..e4934a8 100644 --- a/src/Layout.ts +++ b/src/Layout.ts @@ -64,6 +64,9 @@ export class LayoutSystem /** {@link ContentController} controller is a class for controlling layouts children. */ content: ContentController; + /** Stores isPortrait state */ + isPortrait: boolean; + /** * Creates layout system instance. * @param options - Layout options @@ -95,8 +98,8 @@ export class LayoutSystem } // order here is important as controllers are dependent on each other - this._style = new StyleController(this, options?.styles); this.size = new SizeController(this); + this._style = new StyleController(this, options?.styles); this.align = new AlignController(this); this.content = new ContentController( this, @@ -112,7 +115,9 @@ export class LayoutSystem */ resize(parentWidth: number, parentHeight: number) { - this._style.applyConditionalStyles(parentWidth, parentHeight); + this.isPortrait = parentWidth < parentHeight; + + this._style.applyConditionalStyles(); this.size.resize(parentWidth, parentHeight); } @@ -199,17 +204,18 @@ export class LayoutSystem { const rootLayout = this.getRootLayout(); - rootLayout.layout.size.resize(); + rootLayout.size.resize(); } - protected getRootLayout(): Container + /** Returns root layout of the layout tree. */ + getRootLayout(): LayoutSystem { if (this.container.parent?.layout) { return this.container.parent.layout.getRootLayout(); } - return this.container; + return this; } /** @@ -219,7 +225,6 @@ export class LayoutSystem setStyles(styles: Styles) { this._style.set(styles); - this._style.applyConditionalStyles(); this.updateParents(); } @@ -234,6 +239,12 @@ export class LayoutSystem { return this._style.getAll(); } + + /** Returns true if root layout is in landscape mode. */ + get isRootLayoutPortrait(): boolean + { + return this.getRootLayout().isPortrait === true; + } } /** diff --git a/src/controllers/ContentController.ts b/src/controllers/ContentController.ts index 2a8c6bd..0bbda7e 100644 --- a/src/controllers/ContentController.ts +++ b/src/controllers/ContentController.ts @@ -220,27 +220,6 @@ export class ContentController child.resize(width, height); } }); - - this.updateTextStyles(); - this.updateVisibility(); - } - - /** Updates text styles of all children */ - protected updateTextStyles() - { - this.children.forEach((child) => - { - if (child instanceof Text) - { - child.style = this.layout.textStyle; - } - }); - } - - /** Updates visibility of the layout */ - protected updateVisibility() - { - this.layout.container.visible = this.layout.style?.visible !== false; } protected get newID(): string diff --git a/src/controllers/StyleController.ts b/src/controllers/StyleController.ts index 8be6077..c013baf 100644 --- a/src/controllers/StyleController.ts +++ b/src/controllers/StyleController.ts @@ -14,12 +14,6 @@ export class StyleController /** Holds all text related styles. This is to be nested by children */ protected _textStyle: Partial = {}; // this is to be nested by children - /** Stores last parent width */ - protected parentWidth = 0; - - /** Stores last parent height */ - protected parentHeight = 0; - /** Stores default styles. */ protected defaultStyles: Styles; @@ -172,79 +166,23 @@ export class StyleController return this.styles.opacity; } - /** - * Checks and applies conditional styles basing on parent size - * @param {number} parentWidth - * @param {number} parentHeight - */ - applyConditionalStyles(parentWidth?: number, parentHeight?: number) + /** Checks and applies conditional styles basing on parent size */ + applyConditionalStyles() { - if (parentWidth !== undefined) - { - this.parentWidth = parentWidth; - } - - if (parentHeight !== undefined) - { - this.parentHeight = parentHeight; - } + if (!this.hasConditionalStyles) return; let finalStyles = { ...this.defaultStyles }; - if (this.conditionalStyles.portrait && this.parentHeight >= this.parentWidth) + if (this.conditionalStyles.portrait && this.layout.isRootLayoutPortrait) { finalStyles = { ...finalStyles, ...this.conditionalStyles.portrait }; } - if (this.conditionalStyles.landscape && this.parentHeight < this.parentWidth) + if (this.conditionalStyles.landscape && !this.layout.isRootLayoutPortrait) { finalStyles = { ...finalStyles, ...this.conditionalStyles.landscape }; } - if (this.conditionalStyles.max?.height) - { - for (const [key, value] of Object.entries(this.conditionalStyles.max.height)) - { - if (this.parentHeight <= parseInt(key, 10)) - { - finalStyles = { ...finalStyles, ...value }; - } - } - } - - if (this.conditionalStyles.max?.width) - { - for (const [key, value] of Object.entries(this.conditionalStyles.max.width)) - { - if (this.parentWidth <= parseInt(key, 10)) - { - finalStyles = { ...finalStyles, ...value }; - } - } - } - - if (this.conditionalStyles.min?.height) - { - for (const [key, value] of Object.entries(this.conditionalStyles.min.height)) - { - if (this.parentHeight >= parseInt(key, 10)) - { - finalStyles = { ...finalStyles, ...value }; - } - } - } - - if (this.conditionalStyles.min?.width) - { - for (const [key, value] of Object.entries(this.conditionalStyles.min.width)) - { - if (this.parentWidth >= parseInt(key, 10)) - { - finalStyles = { ...finalStyles, ...value }; - } - } - } - this.set(finalStyles); } @@ -254,7 +192,7 @@ export class StyleController */ protected separateConditionalStyles(styles?: Styles & ConditionalStyles) { - if (!styles.portrait && !styles.landscape && !styles.max && !styles.min) + if (!styles.portrait && !styles.landscape) { this.defaultStyles = { ...styles, @@ -265,30 +203,29 @@ export class StyleController if (styles.portrait) { - this.conditionalStyles.portrait = styles.portrait; - delete styles.portrait; + this.conditionalStyles.portrait = { + ...this.conditionalStyles.portrait, + ...styles.portrait + }; } if (styles.landscape) { - this.conditionalStyles.landscape = styles.landscape; - delete styles.landscape; + this.conditionalStyles.landscape = { + ...this.conditionalStyles.landscape, + ...styles.landscape + }; } - if (styles.max) - { - this.conditionalStyles.max = styles.max; - delete styles.max; - } + delete styles.portrait; + delete styles.landscape; - if (styles.min) - { - this.conditionalStyles.min = styles.min; - delete styles.min; - } + this.defaultStyles = styles; + } - this.defaultStyles = { - ...styles, - }; + /** Returns true if there are conditional styles */ + get hasConditionalStyles(): boolean + { + return Object.keys(this.conditionalStyles).length > 0; } } diff --git a/src/stories/styles/ConditionalAppLayout.stories.ts b/src/stories/styles/ConditionalAppLayout.stories.ts new file mode 100644 index 0000000..0340006 --- /dev/null +++ b/src/stories/styles/ConditionalAppLayout.stories.ts @@ -0,0 +1,190 @@ +import { Layout } from '../../Layout'; +import { argTypes, getDefaultArgs } from '../utils/argTypes'; +import { Container } from '@pixi/display'; +import { VERTICAL_ALIGN, ALIGN, POSITION, CSS_COLOR_NAMES } from '../../utils/constants'; +import { LayoutStyles } from '../../utils/types'; + +const args = { + color: '#000000', + width: 100, + height: 100, + opacity: 1, + childWidth: 50, + childHeight: 34, + textAlign: ALIGN, + verticalAlign: VERTICAL_ALIGN, + position: POSITION +}; + +class LayoutStory +{ + private layout: Layout; + view = new Container(); + + constructor({ + color, + width, + height, + opacity, + textAlign, + position, + childWidth, + childHeight, + verticalAlign + }: any) + { + const fontStyle = { + textAlign, + verticalAlign, + color, + overflow: 'hidden' + }; + + const contentStyles = { + display: 'block', + borderRadius: 20, + ...fontStyle, + landscape: { + width: `${childWidth}%`, + height: `${childHeight}%`, + }, + portrait: { + width: `100%`, + height: `17%`, + } + }; + + // Styles for all elements + const globalStyles: LayoutStyles = { + root: { + color, + width: `${width}%`, + height: `${height}%`, + opacity, + position + }, + header: { + display: 'block', + position: 'top', + background: 'red', + height: '10%', + borderRadius: 20, + ...fontStyle + }, + layoutContent: { + display: 'block', + position: 'center', + height: '70%', + overflow: 'hidden' + }, + leftMenu: { + display: 'block', + width: '30%', + height: '97%', + position: 'left', + landscape: { + visible: true + }, + portrait: { + visible: false + }, + }, + leftMenuContent: { + display: 'block', + height: '100%', + borderRadius: 20, + background: 'blue', + ...fontStyle + }, + mainContent: { + display: 'block', + height: '96%', + position: 'right', + textAlign, + color, + landscape: { + width: '70%', + }, + portrait: { + width: '100%', + }, + }, + mainContent1: { ...contentStyles, background: Object.keys(CSS_COLOR_NAMES)[10] }, + mainContent2: { ...contentStyles, background: Object.keys(CSS_COLOR_NAMES)[20] }, + mainContent3: { ...contentStyles, background: Object.keys(CSS_COLOR_NAMES)[30] }, + mainContent4: { ...contentStyles, background: Object.keys(CSS_COLOR_NAMES)[40] }, + mainContent5: { ...contentStyles, background: Object.keys(CSS_COLOR_NAMES)[50] }, + mainContent6: { ...contentStyles, background: Object.keys(CSS_COLOR_NAMES)[60] }, + footer: { + display: 'block', + position: 'bottom', + background: 'green', + height: '10%', + borderRadius: 20, + ...fontStyle + } + }; + + // Component usage + this.layout = new Layout({ + id: 'root', + content: { + header: { + content: 'Header' + }, + layoutContent: { + content: { + // array of children + leftMenu: { + content: { + id: 'leftMenuContent', + content: 'Left menu' + } + }, + mainContent: { + content: { + mainContent1: { + content: Object.keys(CSS_COLOR_NAMES)[10] + }, + mainContent2: { + content: Object.keys(CSS_COLOR_NAMES)[20] + }, + mainContent3: { + content: Object.keys(CSS_COLOR_NAMES)[30] + }, + mainContent4: { + content: Object.keys(CSS_COLOR_NAMES)[40] + }, + mainContent5: { + content: Object.keys(CSS_COLOR_NAMES)[50] + }, + mainContent6: { + content: Object.keys(CSS_COLOR_NAMES)[60] + } + } + } + } + }, + footer: { + content: 'Footer' + } + }, + globalStyles + }); + + this.view.addChild(this.layout); + } + + resize(w: number, h: number) + { + this.layout.resize(w, h); + } +} + +export const ConditionalApplicationLayout = (params: any) => new LayoutStory(params); + +export default { + title: 'Styles', + argTypes: argTypes(args), + args: getDefaultArgs(args) +}; diff --git a/src/stories/styles/ConditionalStylesMax.stories.ts b/src/stories/styles/ConditionalStylesMax.stories.ts deleted file mode 100644 index c13b15d..0000000 --- a/src/stories/styles/ConditionalStylesMax.stories.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Layout } from '../../Layout'; -import { argTypes, getDefaultArgs } from '../utils/argTypes'; -import { Container } from '@pixi/display'; -import { Text } from '@pixi/text'; - -const args = { - maxWidth: 500, - maxHeight: 500, - maxWidthColor: 'red', - maxHeightColor: 'blue', -}; - -type Props = { - maxWidth: number, - maxHeight: number, - maxWidthColor: string, - maxHeightColor: string -}; - -class LayoutStory -{ - private layout: Layout; - private props: Props; - view = new Container(); - w: number; - h: number; - content: Text; - - constructor(props: Props) - { - this.props = props; - - this.content = new Text(this.generateText(0, 0)); - - this.layout = new Layout({ - content: this.content, - styles: { - padding: 50, - fontSize: 20, - position: 'center', - overflow: 'hidden', - borderRadius: 20, - background: 'white', - maxWidth: '100%', - maxHeight: '100%', - max: { - width: { - [props.maxWidth]: { - background: props.maxWidthColor, - }, - }, - height: { - [props.maxHeight]: { - background: props.maxHeightColor, - }, - } - } - }, - }); - - this.resize(this.w, this.h); - - this.view.addChild(this.layout); - } - - private generateText(w: number, h: number) - { - if (!this.props?.maxWidth && !this.props?.maxHeight) return ''; - - return `For width less than ${this.props.maxWidth} color will be ${this.props.maxWidthColor}. -For height less than ${this.props.maxHeight} color will be ${this.props.maxHeightColor}. - -If non is applied, color will be white - -Width: ${w} -Height: ${h}. - -* width styles always will have priority over height styles`; - } - - resize(w: number, h: number) - { - this.w = w; - this.h = h; - - if (this.props) - { - this.content.text = this.generateText(w, h); - } - - this.layout?.resize(w, h); - } -} - -export const Max = (params: any) => new LayoutStory(params); - -export default { - title: 'Styles', - argTypes: argTypes(args), - args: getDefaultArgs(args), -}; diff --git a/src/stories/styles/ConditionalStylesMin.stories.ts b/src/stories/styles/ConditionalStylesMin.stories.ts deleted file mode 100644 index a642d85..0000000 --- a/src/stories/styles/ConditionalStylesMin.stories.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Layout } from '../../Layout'; -import { argTypes, getDefaultArgs } from '../utils/argTypes'; -import { Container } from '@pixi/display'; -import { Text } from '@pixi/text'; - -const args = { - minWidth: 500, - minHeight: 500, - minWidthColor: 'red', - minHeightColor: 'blue', -}; - -type Props = { - minWidth: number, - minHeight: number, - minWidthColor: string, - minHeightColor: string -}; - -class LayoutStory -{ - private layout: Layout; - private props: Props; - view = new Container(); - w: number; - h: number; - content: Text; - - constructor(props) - { - this.props = props; - - this.content = new Text(this.generateText(0, 0)); - - this.layout = new Layout({ - content: this.content, - styles: { - padding: 50, - fontSize: 20, - position: 'center', - overflow: 'hidden', - borderRadius: 20, - background: 'white', - maxWidth: '100%', - maxHeight: '100%', - min: { - width: { - [props.minWidth]: { - background: props.minWidthColor, - }, - }, - height: { - [props.minHeight]: { - background: props.minHeightColor, - }, - } - } - }, - }); - - this.resize(this.w, this.h); - - this.view.addChild(this.layout); - } - - private generateText(w: number, h: number) - { - if (!this.props?.minWidth && !this.props?.minHeight) return ''; - - return `For width more than ${this.props.minWidth} color will be ${this.props.minWidthColor}. -For height more than ${this.props.minHeight} color will be ${this.props.minHeightColor}. - -If non is applied, color will be white - -Width: ${w} -Height: ${h}. - -* width styles always will have priority over height styles`; - } - - resize(w: number, h: number) - { - this.w = w; - this.h = h; - - if (this.props) - { - this.content.text = this.generateText(w, h); - } - - this.layout?.resize(w, h); - } -} - -export const Min = (params: any) => new LayoutStory(params); - -export default { - title: 'Styles', - argTypes: argTypes(args), - args: getDefaultArgs(args), -}; diff --git a/src/stories/styles/ConditionalStylesPortraitLandscape.stories.ts b/src/stories/styles/ConditionalStylesPortraitLandscape.stories.ts index fe0e852..0938af0 100644 --- a/src/stories/styles/ConditionalStylesPortraitLandscape.stories.ts +++ b/src/stories/styles/ConditionalStylesPortraitLandscape.stories.ts @@ -42,7 +42,6 @@ class LayoutStory fontSize: 40, position: 'center', color: portraitColor, - visible: false, portrait: { visible: false, }, diff --git a/src/utils/types.ts b/src/utils/types.ts index e0d8503..923beaf 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -34,8 +34,6 @@ export type AspectRatio = 'static' | 'flex'; export type ConditionalStyles = { portrait?: Styles; landscape?: Styles; - max?: StylesCondition, - min?: StylesCondition, }; export type Styles = Partial & { @@ -77,15 +75,6 @@ export type Styles = Partial & { visible?: boolean; }; -type StylesCondition = { - width?: { - [K: number]: Styles; - }, - height?: { - [K: number]: Styles; - } -}; - export type LayoutStyles = { [K: string]: Styles & ConditionalStyles; };