Parse theme from a custom simpler format

This commit is contained in:
miruka 2019-07-23 03:14:02 -04:00
parent cb1b95766c
commit 9397687122
9 changed files with 308 additions and 221 deletions

View File

@ -2,6 +2,7 @@
- `QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling)`
- Refactoring
- Sendbox
- Use .mjs modules
- SignIn/RememberAccount screens
- SignIn must be in a flickable
@ -14,8 +15,7 @@
- When qml syntax highlighting supports string interpolation, use them
- Fixes
- Wrong typing notification sending before alias completed
- Message after daybreak delegate
- Message position after daybreak delegate
- Keyboard flicking against top/bottom edge
- Don't strip user spacing in html
- Past events loading (limit 100) freezes the GUI - need to move upsert func

View File

@ -3,7 +3,7 @@
import asyncio
import random
from typing import Any, Dict, Optional, Set, Tuple
from typing import Dict, Optional, Set, Tuple
from .app import App
from .events import users
@ -104,8 +104,13 @@ class Backend:
# General functions
async def load_settings(self) -> Tuple[Dict[str, Any], ...]:
return (await self.ui_settings.read(), await self.ui_state.read())
async def load_settings(self) -> tuple:
from .config_files import Theme
settings = await self.ui_settings.read()
ui_state = await self.ui_state.read()
theme = await Theme(self, settings["theme"]).read()
return (settings, ui_state, theme)
async def request_user_update_event(self, user_id: str) -> None:

View File

@ -10,6 +10,7 @@ import aiofiles
from dataclasses import dataclass, field
from .backend import Backend
from .theme_parser import convert_to_qml
JsonData = Dict[str, Any]
@ -18,16 +19,32 @@ WRITE_LOCK = asyncio.Lock()
@dataclass
class ConfigFile:
backend: Backend = field(repr=False)
filename: str = field()
use_data_dir: bool = False
backend: Backend = field(repr=False)
filename: str = field()
@property
def path(self) -> Path:
# pylint: disable=no-member
dirs = self.backend.app.appdirs
to = dirs.user_data_dir if self.use_data_dir else dirs.user_config_dir
return Path(to) / self.filename
return Path(self.backend.app.appdirs.user_config_dir) / self.filename
async def default_data(self):
return ""
async def read(self):
try:
return self.path.read_text()
except FileNotFoundError:
return await self.default_data()
async def write(self, data) -> None:
async with WRITE_LOCK:
self.path.parent.mkdir(parents=True, exist_ok=True)
async with aiofiles.open(self.path, "w") as new:
await new.write(data)
@dataclass
@ -38,8 +55,8 @@ class JSONConfigFile(ConfigFile):
async def read(self) -> JsonData:
try:
data = json.loads(self.path.read_text())
except (json.JSONDecodeError, FileNotFoundError):
data = json.loads(await super().read())
except json.JSONDecodeError:
data = {}
return {**await self.default_data(), **data}
@ -47,12 +64,7 @@ class JSONConfigFile(ConfigFile):
async def write(self, data: JsonData) -> None:
js = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)
async with WRITE_LOCK:
self.path.parent.mkdir(parents=True, exist_ok=True)
async with aiofiles.open(self.path, "w") as new:
await new.write(js)
await super().write(js)
@dataclass
@ -90,14 +102,20 @@ class UISettings(JSONConfigFile):
async def default_data(self) -> JsonData:
return {
"writeAliases": {}
"theme": "Default.qpl",
"writeAliases": {},
}
@dataclass
class UIState(JSONConfigFile):
filename: str = "ui-state.json"
use_data_dir: bool = True
filename: str = "ui-state.json"
@property
def path(self) -> Path:
# pylint: disable=no-member
return Path(self.backend.app.appdirs.user_data_dir) / self.filename
async def default_data(self) -> JsonData:
return {
@ -107,3 +125,30 @@ class UIState(JSONConfigFile):
"pageProperties": {},
"sidePaneManualWidth": None,
}
@dataclass
class Theme(ConfigFile):
@property
def path(self) -> Path:
# pylint: disable=no-member
data_dir = Path(self.backend.app.appdirs.user_data_dir)
user_file = data_dir / "themes" / self.filename
if user_file.exists():
return user_file
return Path("src") / "themes" / self.filename
async def default_data(self) -> str:
async with aiofiles.open("src/themes/Default.qpl", "r") as file:
return file.read()
async def read(self) -> str:
return convert_to_qml(await super().read())
async def write(self, data: str) -> None:
raise NotImplementedError()

View File

@ -0,0 +1,69 @@
# Copyright 2019 miruka
# This file is part of harmonyqml, licensed under LGPLv3.
import re
from typing import Generator
PROPERTY_TYPES = {"bool", "double", "int", "list", "real", "string", "url",
"var", "date", "point", "rect", "size", "color"}
def _add_property(line: str) -> str:
if re.match(r"^\s*[a-zA-Z0-9_]+\s*:$", line):
return re.sub(r"^(\s*)(\S*\s*):$",
r"\1readonly property QtObject \2: QtObject",
line)
types = "|".join(PROPERTY_TYPES)
if re.match(fr"^\s*({types}) [a-zA-Z\d_]+\s*:", line):
return re.sub(r"^(\s*)(\S*)", r"\1readonly property \2", line)
return line
def _process_lines(content: str) -> Generator[str, None, None]:
skip = False
indent = " " * 4
current_indent = 0
for line in content.split("\n"):
line = line.rstrip()
if not line.strip() or line.strip().startswith("//"):
continue
start_space_list = re.findall(r"^ +", line)
start_space = start_space_list[0] if start_space_list else ""
line_indents = len(re.findall(indent, start_space))
if not skip:
if line_indents > current_indent:
yield "%s{" % (indent * current_indent)
current_indent = line_indents
while line_indents < current_indent:
current_indent -= 1
yield "%s}" % (indent * current_indent)
line = _add_property(line)
yield line
skip = any((line.endswith(e) for e in "([{+\\,?:"))
while current_indent:
current_indent -= 1
yield "%s}" % (indent * current_indent)
def convert_to_qml(theme_content: str) -> str:
lines = [
"import QtQuick 2.12",
'import "utils.js" as Ut',
"QtObject {",
" id: theme",
]
lines += [f" {line}" for line in _process_lines(theme_content)]
lines += ["}"]
return "\n".join(lines)

View File

@ -105,7 +105,7 @@ HRectangle {
vals.reduce((a, b) => a.length > b.length ? a: b)
let textNotStartsWithAnyAlias =
! vals.some(a => text.startsWith(a))
! vals.some(a => a.startsWith(text))
let textContainsCharNotInAnyAlias =
vals.every(a => text.split("").some(c => ! a.includes(c)))

View File

@ -53,9 +53,10 @@ Python {
call("APP.is_debug_on", [Qt.application.arguments], on => {
window.debug = on
callCoro("load_settings", [], ([settings, uiState]) => {
callCoro("load_settings", [], ([settings, uiState, theme]) => {
window.settings = settings
window.uiState = uiState
window.theme = Qt.createQmlObject(theme, window, "theme")
callCoro("saved_accounts.any_saved", [], any => {
py.ready = true

View File

@ -1,194 +0,0 @@
// Copyright 2019 miruka
// This file is part of harmonyqml, licensed under LGPLv3.
import QtQuick 2.12
import "utils.js" as Ut
QtObject {
id: theme
property int minimumSupportedWidth: 240
property int minimumSupportedHeight: 120
property int contentIsWideAbove: 439
property int minimumSupportedWidthPlusSpacing: 240 + spacing * 2
property int minimumSupportedHeightPlusSpacing: 120 + spacing * 2
property int baseElementsHeight: 36
property int spacing: 8
property int animationDuration: 100
property QtObject fontSize: QtObject {
property int smallest: 6
property int smaller: 8
property int small: 13
property int normal: 16
property int big: 22
property int bigger: 32
property int biggest: 48
}
property QtObject fontFamily: QtObject {
property string sans: "SFNS Display"
property string serif: "Roboto Slab"
property string mono: "Hack"
}
property int radius: 5
property QtObject colors: QtObject {
property color background0: Ut.hsla(0, 0, 90, 0.5)
property color background1: Ut.hsla(0, 0, 90, 0.6)
property color background2: Ut.hsla(0, 0, 90, 0.7)
property color foreground: "black"
property color foregroundDim: Ut.hsl(0, 0, 20)
property color foregroundDim2: Ut.hsl(0, 0, 30)
property color foregroundError: Ut.hsl(342, 64, 32)
property color textBorder: Ut.hsla(0, 0, 0, 0.07)
property color accent: Ut.hsl(25, 60, 50)
property color accentDarker: Ut.hsl(25, 60, 35)
}
property QtObject controls: QtObject {
property QtObject button: QtObject {
property color background: colors.background2
}
property QtObject interactiveRectangle: QtObject {
property color background: "transparent"
property color hoveredBackground: Ut.hsla(0, 0, 0, 0.2)
property color pressedBackground: Ut.hsla(0, 0, 0, 0.4)
property color checkedBackground: Ut.hsla(0, 0, 0, 0.4)
}
property QtObject textField: QtObject {
property color background: colors.background2
property color border: "transparent"
property color focusedBackground: background
property color focusedBorder: colors.accent
property int borderWidth: 1
}
property QtObject textArea: QtObject {
property color background: colors.background2
}
}
property QtObject sidePane: QtObject {
property real autoWidthRatio: 0.33
property int maximumAutoWidth: 320
property int autoCollapseBelowWidth: 128
property int collapsedWidth: avatar.size
property int autoReduceBelowWindowWidth:
minimumSupportedWidthPlusSpacing + collapsedWidth
property color background: colors.background2
property QtObject account: QtObject {
property color background: Qt.lighter(colors.background2, 1.05)
}
property QtObject settingsButton: QtObject {
property color background: colors.background2
}
property QtObject filterRooms: QtObject {
property color background: colors.background2
}
}
property QtObject chat: QtObject {
property QtObject selectViewBar: QtObject {
property color background: colors.background2
}
property QtObject roomHeader: QtObject {
property color background: colors.background2
}
property QtObject eventList: QtObject {
property int ownEventsOnRightUnderWidth: 768
property color background: "transparent"
}
property QtObject message: QtObject {
property color ownBackground: Ut.hsla(25, 40, 82, 0.7)
property color background: colors.background2
property color body: colors.foreground
property color date: colors.foregroundDim
property color link: colors.accentDarker
// property color code: Ut.hsl(0, 0, 80)
// property color codeBackground: Ut.hsl(0, 0, 10)
property color code: Ut.hsl(265, 60, 35)
property color greenText: Ut.hsl(80, 60, 25)
property string styleSheet:
"a { color: " + link + " }" +
"code { font-family: " + fontFamily.mono + "; " +
"color: " + code + " }" +
"h1, h2 { font-weight: normal }" +
"h6 { font-size: small }" +
".greentext { color: " + greenText + " }"
property string styleInclude:
'<style type"text/css">\n' + styleSheet + '\n</style>\n'
}
property QtObject daybreak: QtObject {
property color background: colors.background2
property color foreground: colors.foreground
property int radius: theme.radius
}
property QtObject inviteBanner: QtObject {
property color background: colors.background2
}
property QtObject leftBanner: QtObject {
property color background: colors.background2
}
property QtObject unknownDevices: QtObject {
property color background: colors.background2
}
property QtObject typingMembers: QtObject {
property color background: colors.background1
}
property QtObject sendBox: QtObject {
property color background: colors.background2
}
}
property color pageHeadersBackground: colors.background2
property QtObject box: QtObject {
property color background: colors.background0
property int radius: theme.radius
}
property QtObject avatar: QtObject {
property int size: baseElementsHeight
property int radius: theme.radius
property color letter: "white"
property QtObject background: QtObject {
property real saturation: 0.22
property real lightness: 0.5
property real alpha: 1
property color unknown: Ut.hsl(0, 0, 22)
}
}
property QtObject displayName: QtObject {
property real saturation: 0.32
property real lightness: 0.3
}
}

View File

@ -8,8 +8,8 @@ import "Models"
ApplicationWindow {
id: window
minimumWidth: theme.minimumSupportedWidth
minimumHeight: theme.minimumSupportedHeight
minimumWidth: theme ? theme.minimumSupportedWidth : 240
minimumHeight: theme ? theme.minimumSupportedHeight : 120
width: 640
height: 480
visible: true
@ -34,7 +34,8 @@ ApplicationWindow {
property var uiState: ({})
onUiStateChanged: py.saveConfig("ui_state", uiState)
Theme { id: theme }
property var theme: null
Shortcuts { id: shortcuts}
Python { id: py }

160
src/themes/Default.qpl Normal file
View File

@ -0,0 +1,160 @@
// Copyright 2019 miruka
// This file is part of harmonyqml, licensed under LGPLv3.
// vim: syntax=qml
int minimumSupportedWidth: 240
int minimumSupportedHeight: 120
int contentIsWideAbove: 439
int minimumSupportedWidthPlusSpacing: 240 + spacing * 2
int minimumSupportedHeightPlusSpacing: 120 + spacing * 2
int baseElementsHeight: 36
int spacing: 8
int radius: 5
int animationDuration: 100
color pageHeadersBackground: colors.background2
fontSize:
int smallest: 6
int smaller: 8
int small: 13
int normal: 16
int big: 22
int bigger: 32
int biggest: 48
fontFamily:
string sans: "SFNS Display"
string serif: "Roboto Slab"
string mono: "Hack"
colors:
color background0: Ut.hsla(0, 0, 90, 0.5)
color background1: Ut.hsla(0, 0, 90, 0.6)
color background2: Ut.hsla(0, 0, 90, 0.7)
color foreground: "black"
color foregroundDim: Ut.hsl(0, 0, 20)
color foregroundDim2: Ut.hsl(0, 0, 30)
color foregroundError: Ut.hsl(342, 64, 32)
color textBorder: Ut.hsla(0, 0, 0, 0.07)
color accent: Ut.hsl(25, 60, 50)
color accentDarker: Ut.hsl(25, 60, 35)
controls:
button:
color background: colors.background2
interactiveRectangle:
color background: "transparent"
color hoveredBackground: Ut.hsla(0, 0, 0, 0.2)
color pressedBackground: Ut.hsla(0, 0, 0, 0.4)
color checkedBackground: Ut.hsla(0, 0, 0, 0.4)
textField:
color background: colors.background2
color border: "transparent"
color focusedBackground: background
color focusedBorder: colors.accent
int borderWidth: 1
textArea:
color background: colors.background2
sidePane:
real autoWidthRatio: 0.33
int maximumAutoWidth: 320
int autoCollapseBelowWidth: 128
int collapsedWidth: avatar.size
int autoReduceBelowWindowWidth:
minimumSupportedWidthPlusSpacing + collapsedWidth
color background: colors.background2
account:
color background: Qt.lighter(colors.background2, 1.05)
settingsButton:
color background: colors.background2
filterRooms:
color background: colors.background2
chat:
selectViewBar:
color background: colors.background2
roomHeader:
color background: colors.background2
eventList:
int ownEventsOnRightUnderWidth: 768
color background: "transparent"
message:
color ownBackground: Ut.hsla(25, 40, 82, 0.7)
color background: colors.background2
color body: colors.foreground
color date: colors.foregroundDim
color link: colors.accentDarker
// color code: Ut.hsl(0, 0, 80)
// color codeBackground: Ut.hsl(0, 0, 10)
color code: Ut.hsl(265, 60, 35)
color greenText: Ut.hsl(80, 60, 25)
string styleSheet:
"a { color: " + link + " }" +
"code { font-family: " + fontFamily.mono + "; " +
"color: " + code + " }" +
"h1, h2 { font-weight: normal }" +
"h6 { font-size: small }" +
".greentext { color: " + greenText + " }"
string styleInclude:
'<style type"text/css">\n' + styleSheet + '\n</style>\n'
daybreak:
color background: colors.background2
color foreground: colors.foreground
int radius: theme.radius
inviteBanner:
color background: colors.background2
leftBanner:
color background: colors.background2
unknownDevices:
color background: colors.background2
typingMembers:
color background: colors.background1
sendBox:
color background: colors.background2
box:
color background: colors.background0
int radius: theme.radius
avatar:
int size: baseElementsHeight
int radius: theme.radius
color letter: "white"
background:
real saturation: 0.22
real lightness: 0.5
real alpha: 1
color unknown: Ut.hsl(0, 0, 22)
displayName:
real saturation: 0.32
real lightness: 0.3