-
-
Save falkoschindler/bc1eacad35ac455328110a4fcc33c3d0 to your computer and use it in GitHub Desktop.
Demonstrates a new style definition API using a builder pattern, literal keyword strings and re-usable style properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from copy import deepcopy | |
from typing import Literal, Optional, overload | |
from nicegui import ui | |
# This example demonstrates how to replace the style method with an instance of a Style class. | |
# The instance can be called like the original method. | |
# But it also has fields for manipulating individual style properties. | |
# Each field is an instance of a re-usable style property class with overloads for different units. | |
# For string values there are overloads with literal types for predefined values. | |
class ColorProperty: | |
def __init__(self, style: 'Style', key: str) -> None: | |
self.style = style | |
self.key = key | |
@overload | |
def __call__(self, color: Literal['red', 'green', 'blue', 'yellow', 'purple', 'orange', 'black', 'white', 'gray']) -> 'Style': | |
'''Use a predefined color name.''' | |
@overload | |
def __call__(self, value: str) -> 'Style': | |
'''Use an arbitrary value.''' | |
def __call__(self, value: str) -> 'Style': | |
self.style.element._style[self.key] = value | |
return self.style | |
class DimensionProperty: | |
def __init__(self, style: 'Style', key: str) -> None: | |
self.style = style | |
self.key = key | |
@overload | |
def __call__(self, px: float) -> 'Style': | |
'''Use a value in pixels.''' | |
@overload | |
def __call__(self, rem: float) -> 'Style': | |
'''Use a value in rem.''' | |
@overload | |
def __call__(self, percent: float) -> 'Style': | |
'''Use a value in percent.''' | |
@overload | |
def __call__(self, value: Literal['auto', 'max-content', 'min-content', 'fit-content']) -> 'Style': | |
'''Use a predefined value.''' | |
@overload | |
def __call__(self, value: str) -> 'Style': | |
'''Use an arbitrary value.''' | |
def __call__(self, value: str = ..., *, px: float = ..., rem: float = ..., percent: float = ...) -> 'Style': | |
if value is not ...: | |
self.style.element._style[self.key] = value | |
elif px is not ...: | |
self.style.element._style[self.key] = f'{px}px' | |
elif rem is not ...: | |
self.style.element._style[self.key] = f'{rem}rem' | |
elif percent is not ...: | |
self.style.element._style[self.key] = f'{percent}%' | |
else: | |
raise TypeError(f'{self.key} dimension requires either px, rem or percent') | |
return self.style | |
class Style: | |
def __init__(self, element: ui.element) -> None: | |
self.element = element | |
self.color = ColorProperty(self, 'color') | |
'''Set the color of the element.''' | |
self.background_color = ColorProperty(self, 'background-color') | |
'''Set the background color of the element.''' | |
self.width = DimensionProperty(self, 'width') | |
'''Set the width of the element.''' | |
self.height = DimensionProperty(self, 'height') | |
'''Set the height of the element.''' | |
self.margin = DimensionProperty(self, 'margin') | |
'''Set the margin of the element.''' | |
self.padding = DimensionProperty(self, 'padding') | |
'''Set the padding of the element.''' | |
def __call__(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None): | |
'''CSS style sheet definitions to modify the look of the element. | |
Every style in the `remove` parameter will be removed from the element. | |
Styles are separated with a semicolon. | |
This can be helpful if the predefined style sheet definitions by NiceGUI are not wanted in a particular styling. | |
''' | |
# NOTE: This is the original implementation of the style method. | |
style_dict = deepcopy(self.element._style) if replace is None else {} | |
for key in self.element._parse_style(remove): | |
if key in style_dict: | |
del style_dict[key] | |
style_dict.update(self.element._parse_style(add)) | |
style_dict.update(self.element._parse_style(replace)) | |
if self.element._style != style_dict: | |
self.element._style = style_dict | |
self.element.update() | |
return self.element | |
label = ui.label('Hello World!') | |
label.style = Style(label) # NOTE: monkey-patch label.style, will be done in ui.element | |
label.style.color('blue').background_color('orange').width(px=150).padding(rem=1).margin('auto') | |
ui.run(port=4321) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment