Skip to content

Instantly share code, notes, and snippets.

@falkoschindler
Created February 5, 2023 13:29
Show Gist options
  • Save falkoschindler/04f66d178193c3f578c78441818ae647 to your computer and use it in GitHub Desktop.
Save falkoschindler/04f66d178193c3f578c78441818ae647 to your computer and use it in GitHub Desktop.
Demonstrates a new style definition API using a builder pattern and literal keyword strings
#!/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 methods itself for manipulating individual style properties.
# Each property has overloads for different units.
# For string values there are overloads with literal types for predefined values.
Color = Literal['red', 'green', 'blue', 'yellow', 'purple', 'orange', 'black', 'white', 'gray']
class Style:
def __init__(self, element: ui.element) -> None:
self.element = 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
@overload
def color(self, color: Color) -> 'Style':
'''Set the color of the element to a predefined color name.'''
@overload
def color(self, value: str) -> 'Style':
'''Set the color of the element to an arbitrary value.'''
def color(self, value: str) -> 'Style':
'''Set the color of the element.'''
self.element._style['color'] = value
return self
@overload
def background_color(self, color: Color) -> 'Style':
'''Set the background color of the element to a predefined color name.'''
@overload
def background_color(self, value: str) -> 'Style':
'''Set the color of the element to an arbitrary value.'''
def background_color(self, value: str) -> 'Style':
'''Set the background color of the element.'''
self.element._style['background-color'] = value
return self
@overload
def width(self, px: float) -> 'Style':
'''Set the width of the element in pixels.'''
@overload
def width(self, rem: float) -> 'Style':
'''Set the width of the element in rem.'''
@overload
def width(self, percent: float) -> 'Style':
'''Set the width of the element in percent.'''
@overload
def width(self, value: Literal['auto', 'max-content', 'min-content', 'fit-content']) -> 'Style':
'''Set the width of the element to a predefined value.'''
@overload
def width(self, value: str) -> 'Style':
'''Set the width of the element to an arbitrary value.'''
def width(self, value: str = ..., *, px: float = ..., rem: float = ..., percent: float = ...) -> 'Style':
return self._set_dimension('width', value, px, rem, percent)
@overload
def height(self, px: float) -> 'Style':
'''Set the height of the element in pixels.'''
@overload
def height(self, rem: float) -> 'Style':
'''Set the height of the element in rem.'''
@overload
def height(self, percent: float) -> 'Style':
'''Set the height of the element in percent.'''
@overload
def height(self, value: Literal['auto', 'max-content', 'min-content', 'fit-content']) -> 'Style':
'''Set the height of the element to a predefined value.'''
@overload
def height(self, value: str) -> 'Style':
'''Set the height of the element to an arbitrary value.'''
def height(self, value: str = ..., *, px: float = ..., rem: float = ..., percent: float = ...) -> 'Style':
'''Set the height of the element.'''
return self._set_dimension('height', value, px, rem, percent)
@overload
def margin(self, px: float) -> 'Style':
'''Set the margin of the element in pixels.'''
@overload
def margin(self, rem: float) -> 'Style':
'''Set the margin of the element in rem.'''
@overload
def margin(self, percent: float) -> 'Style':
'''Set the margin of the element in percent.'''
@overload
def margin(self, value: str) -> 'Style':
'''Set the margin of the element to an arbitrary value.'''
def margin(self, value: str = ..., *, px: float = ..., rem: float = ..., percent: float = ...) -> 'Style':
return self._set_dimension('margin', value, px, rem, percent)
@overload
def padding(self, px: float) -> 'Style':
'''Set the padding of the element in pixels.'''
@overload
def padding(self, rem: float) -> 'Style':
'''Set the padding of the element in rem.'''
@overload
def padding(self, percent: float) -> 'Style':
'''Set the padding of the element in percent.'''
@overload
def padding(self, value: str) -> 'Style':
'''Set the padding of the element to an arbitrary value.'''
def padding(self, value: str = ..., *, px: float = ..., rem: float = ..., percent: float = ...) -> 'Style':
'''Set the padding of the element.'''
return self._set_dimension('padding', value, px, rem, percent)
def _set_dimension(self, key: str, value: str = ..., px: float = ..., rem: float = ..., percent: float = ...) -> 'Style':
if value is not ...:
self.element._style[key] = value
elif px is not ...:
self.element._style[key] = f'{px}px'
elif rem is not ...:
self.element._style[key] = f'{rem}rem'
elif percent is not ...:
self.element._style[key] = f'{percent}%'
else:
raise TypeError(f'{key} dimension requires either px, rem or percent')
return self
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