add theme settings and basic dark theme

This commit is contained in:
Izalia Mae 2022-08-27 19:32:06 -04:00
parent e451ce8b48
commit 3d63f3ea00
12 changed files with 326 additions and 77 deletions

View file

@ -7,7 +7,7 @@ details .container {
margin-left: 20px;
}
details .grid-container {
details:not(.theme) .grid-container {
grid-template-columns: max-content auto max-content;
}
@ -30,3 +30,17 @@ input[type='text'] {
input[type='number'] {
width: 50px;
}
.theme.system summary {
grid-template-columns: max-content auto max-content !important;
}
.theme.user summary {
grid-template-columns: max-content auto max-content max-content !important;
}
.theme {
background: var(--ui-background) !important;
padding: 2px 2px 2px 5px !important;
}

View file

@ -25,6 +25,50 @@
=config_checkbox('Load all tabs on startup', 'load_tabs', config.load_tabs)
=config_checkbox('Load tab when switching if it is unloaded', 'load_switch', config.load_switch)
%details.category.section open
%summary << System Themes
-for theme in themes.system.values()
%details.section.theme.system
%summary.grid-container
%span.grid-item -> =theme.name
-if not themes.current and themes.current_system == theme.hash
%a.grid-item.button href='{{var.local}}/preferences/theme/set/default' << Disable
-else
%a.grid-item.button href='{{var.local}}/preferences/theme/set/{{theme.hash}}' << Enable
.container
.author << Author: {{theme.author}}
-if theme.license
.license << License: {{theme.license}}
-if theme.url
%a.url href='{{theme.url}}' << Website
%details.category.section open
%summary << User Themes
-for theme in themes.user.values()
%details.section.theme.user
%summary.grid-container
%span.grid-item -> =theme.name
%a.grid-item.button href='{{var.local}}/preferences/theme/delete/{{theme.hash}}' << Delete
-if themes.current == theme.hash
%a.grid-item.button href='{{var.local}}/preferences/theme/set/default' << Disable
-else
%a.grid-item.button href='{{var.local}}/preferences/theme/set/{{theme.hash}}' << Enable
.container
.author << Author: {{theme.author}}
-if theme.license
.license << License: {{theme.license}}
-if theme.url
%a.url href='{{theme.url}}' << Website
%details.category.section {{'open' if pass_type == 'bitwarden' else ''}}
%summary << Password Storage: Bitwarden
.container

View file

@ -86,7 +86,7 @@ class ProtocolRequest(ObjectBase):
@property
def window(self):
return self.window
return self.app.window
@property

View file

@ -1,5 +1,6 @@
import objgraph
import mimetypes
import traceback
from jinja2.exceptions import TemplateNotFound
from izzylib import Color, class_name, fuzzy_string_match
@ -414,6 +415,8 @@ def preferences_home(handler, request):
locked = None
)
context['themes'] = handler.window.themes
return request.page('preferences', context)
@ -459,7 +462,7 @@ def preferences_reset(handler, request, key):
@Local.route('/preferences/bitwarden/login/{username}/{password}')
def passwords_bw_login(handler, request, username, password):
def preferences_bw_login(handler, request, username, password):
handler.password.stop()
if not (skey := handler.password.login(username, password, force=True)):
@ -474,7 +477,7 @@ def passwords_bw_login(handler, request, username, password):
@Local.route('/preferences/bitwarden/logout')
def passwords_bw_logout(handler, request):
def preferences_bw_logout(handler, request):
handler.password.stop()
handler.password.logout()
@ -482,7 +485,7 @@ def passwords_bw_logout(handler, request):
@Local.route('/preferences/bitwarden/lock')
def passwords_bw_lock(handler, request):
def preferences_bw_lock(handler, request):
handler.password.stop(lock=True)
with handler.db.session as s:
@ -492,7 +495,7 @@ def passwords_bw_lock(handler, request):
@Local.route('/preferences/bitwarden/unlock/{password}')
def passwords_bw_unlock(handler, request, password):
def preferences_bw_unlock(handler, request, password):
request.query.redir = '/preferences'
handler.password.stop()
@ -507,6 +510,25 @@ def passwords_bw_unlock(handler, request, password):
return request.ok_or_redirect('Unlocked password vault')
@Local.route('/preferences/theme/set/{hash}')
def preferences_theme_set(handler, request, hash):
themes = handler.window.themes
if hash == 'default':
themes.set('default')
message = 'Set theme to system'
else:
try:
theme = themes.set(hash)
message = f'Set theme to {theme.name}'
except KeyError:
return request.error(f'Cannot find theme with hash: {hash}', 404)
return request.redirect('/preferences', message)
### Search ###
@Local.route('/search')
def search_home(handler, request):

View file

@ -30,11 +30,10 @@ entry image:hover {
}
notebook > header.top > tabs > tab {
notebook > header > tabs > tab {
padding: 0px 5px;
}
.entry-recolor {
background-color: @theme_bg_color;
border: 1px solid @theme_bg_color;
@ -49,9 +48,9 @@ notebook > header.top > tabs > tab {
.notification-container {
background-color: @theme_base_color;
padding-left: 8px;
border: 1px solid transparent;
border-radius: 8px 8px 0 0;
padding-left: 8px;
/* border: 1px solid transparent;
border-radius: 8px 8px 0 0;*/
}

View file

@ -199,6 +199,9 @@
</child>
</object>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
<object class="GtkImage" id="notification-close-icon">
<property name="visible">True</property>
@ -378,6 +381,9 @@
</child>
</object>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
<object class="GtkImage" id="statusbar-bookmark-icon">
<property name="visible">True</property>
@ -567,6 +573,9 @@
</child>
</object>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
<object class="GtkImage" id="statusbar-logins-icon">
<property name="visible">True</property>
@ -870,6 +879,9 @@
</child>
</object>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
<object class="GtkImage" id="statusbar-siteoptions-icon">
<property name="visible">True</property>
@ -1056,6 +1068,9 @@
</child>
</object>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
<object class="GtkImage" id="statusbar-toot-icon">
<property name="visible">True</property>
@ -1375,6 +1390,9 @@
</child>
</object>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
<packing>
<property name="expand">False</property>
@ -1476,6 +1494,9 @@
<property name="tab-fill">False</property>
</packing>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
<packing>
<property name="expand">True</property>
@ -1694,6 +1715,9 @@
<property name="position">10</property>
</packing>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
<packing>
<property name="expand">False</property>
@ -1723,6 +1747,7 @@
<object class="GtkLabel" id="notification-message">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">8</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
@ -1750,6 +1775,9 @@
</style>
</object>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
</child>
</object>

View file

@ -303,7 +303,7 @@
</packing>
</child>
<style>
<class name="navbar"/>
<class name="ui-element"/>
</style>
</object>
<object class="GtkImage" id="search-close-icon">
@ -406,5 +406,8 @@
<property name="position">4</property>
</packing>
</child>
<style>
<class name="ui-element"/>
</style>
</object>
</interface>

View file

@ -0,0 +1,9 @@
[info]
name = Dark
author = Zoey Mae
url = https://git.barkshark.xyz/izaliamae/barkshark-web
license = CNPL 4+
[settings]
entry = theme.css
base = none

View file

@ -0,0 +1,64 @@
@define-color background #222222;
@define-color primary #9ED2FF;
@define-color tab shade(@background, 1.35);
@define-color tab_header shade(@background, 1.35);
@define-color text shade(@primary, 1.9);
@define-color tab_gradiant_outer shade(@primary, 0.25);
@define-color tab_gradiant_inner shade(@primary, 0.35);
* {
border-radius: 0;
}
menuitem:hover {
background-color: @primary;
color: @background;
}
notebook tab {
margin: 0px;
}
notebook > stack > box, .ui-element {
background-color: @background;
border: none;
}
notebook > header {
background-color: @tab_header;
border: none;
}
notebook > header > tabs > tab {
background-color: @tab;
}
notebook > header > tabs > tab:checked {
background-color: shade(@primary, 0.30);
}
notebook > header > tabs > tab {
background-image: -gtk-gradient (
linear, center top, center bottom,
color-stop(-0.25, @tab_gradiant_outer), color-stop(0.5, @tab_gradiant_inner), color-stop(1.25, @tab_gradiant_outer)
);
}
popover:first-child {
background-color: transparent;
}
entry, text {
background-color: shade(@primary, 0.15);
border: 1px solid transparent;
}
entry:hover, text:hover {
background-color: shade(@primary, 0.20);
border: 1px solid shade(@primary, 0.20);
}
entry:focus, text:focus {
background-color: shade(@primary, 0.25);
border: 1px solid @primary;
}

View file

@ -0,0 +1,9 @@
[info]
name = Light
author = Zoey Mae
url = https://git.barkshark.xyz/izaliamae/barkshark-web
license = CNPL 4+
[settings]
entry = theme.css
base = none

View file

@ -0,0 +1 @@

View file

@ -1,3 +1,4 @@
import hashlib
import traceback
from configparser import ConfigParser
@ -20,14 +21,13 @@ class Themes(ComponentBase, ObjectBase):
ComponentBase.__init__(self)
ObjectBase.__init__(self,
window = window,
context = Gtk.StyleContext(),
main = None,
current = None,
current_system = None,
watcher = None,
user = DotDict(),
system = DotDict(),
readonly_props = ['window', 'context', 'user', 'system']
readonly_props = ['window', 'user', 'system']
)
self.setup()
@ -40,6 +40,11 @@ class Themes(ComponentBase, ObjectBase):
self.watcher_stop()
@property
def screen(self):
return Gdk.Screen.get_default()
@property
def systempath(self):
return self.app.path.script.join('systhemes')
@ -60,6 +65,16 @@ class Themes(ComponentBase, ObjectBase):
raise KeyError(f'Path not in any theme: {path}')
def get_theme_by_property(self, key, value, system=False):
themes = self.system.values() if system else self.user.values()
for theme in themes:
if theme.get_property(key) == value:
return theme
raise KeyError(f'Cannot find theme with property: {key}="{value}"')
def list_unloaded(self):
for path in self.userpath.listdir(recursive=False):
if path.isdir() and path.name not in self.user:
@ -68,40 +83,79 @@ class Themes(ComponentBase, ObjectBase):
def load_main(self):
if self.main:
self.context.remove_provider(self.main)
Gtk.StyleContext.remove_provider_for_screen(self.screen, self.main)
self.main = Gtk.CssProvider()
self.main.load_from_file(Gio.File.new_for_path(self.app.path.resources.join('main.css')))
self.context.add_provider(self.main, StylePriority.FALLBACK)
Gtk.StyleContext.add_provider_for_screen(self.screen, self.main, StylePriority.FALLBACK)
def set(self, path):
if self.current == path:
return
def set(self, hash, save=True):
theme = None
theme = self.user[path]
if hash == 'default':
self.unset()
self.unset_system()
self.unset()
#logging.verbose('Set theme to system')
if theme.base and theme.base != self.current_system:
self.set_system(theme.base.lower())
else:
if hash in self.system:
theme = self.set_system(hash, True)
self.context.add_provider(theme.get_provider(), StylePriority.USER)
self.current = theme.path
elif hash in self.user:
theme = self.set_user(hash)
else:
raise KeyError(f'Cannot find theme with hash: {hash}') from None
#logging.verbose(f'Set theme to {theme.name}')
if save:
with self.db.session as s:
s.put_config('theme', hash)
return theme
def set_system(self, name):
theme = self.system[name]
def set_user(self, hash):
theme = self.user[hash]
if self.current_system == name:
return
if self.current == hash:
return theme
self.unset_user()
if not theme.base:
self.unset_system()
elif theme.base != self.current_system:
systheme = self.get_theme_by_property('name', theme.base.title(), system=True)
self.set_system(systheme.hash)
Gtk.StyleContext.add_provider_for_screen(self.screen, theme.get_provider(), StylePriority.USER)
self.current = theme.hash
return theme
def set_system(self, hash, unset_user=False):
theme = self.system[hash]
if unset_user:
self.unset_user()
if self.current_system == hash:
return theme
elif self.current_system:
self.unset_system()
self.context.add_provider(theme.get_provider(), StylePriority.APPLICATION)
self.current_system = name
Gtk.StyleContext.add_provider_for_screen(self.screen, theme.get_provider(), StylePriority.APPLICATION)
self.current_system = theme.hash
return theme
def setup(self):
@ -110,37 +164,45 @@ class Themes(ComponentBase, ObjectBase):
for path in self.systempath.listdir(recursive=False):
if path.isdir():
theme = Theme(path)
self.system[theme.name.lower()] = theme
self.system[theme.hash] = theme
logging.verbose(f'Loaded system theme: {theme.name}')
for path in self.list_unloaded():
try:
self.user[path] = Theme(path)
theme = Theme(path)
self.user[theme.hash] = theme
logging.verbose(f'Loaded user theme: {theme.name} by {theme.author}')
except Exception as e:
traceback.print_exc()
with self.db.session as s:
type, _, theme = s.get_config('theme').partition(':')
try:
self.set(s.get_config('theme', 'default'), save=False)
if type == 'system':
self.set_system(theme)
elif type == 'user':
self.set_system(theme)
except KeyError:
logging.warning(f'Theme with hash does not exist: {hash}')
def unset(self):
self.unset_user()
self.unset_system()
def unset_user(self):
if not self.current:
return
self.context.remove_provider(self.user[self.current].provider)
Gtk.StyleContext.remove_provider_for_screen(self.screen, self.user[self.current].provider)
self.current = None
def unset_system(self):
if not self.current_system:
return
self.context.remove_provider(self.system[self.current_system].provider)
Gtk.StyleContext.remove_provider_for_screen(self.screen, self.system[self.current_system].provider)
self.current_system = None
def watcher_start(self):
@ -171,31 +233,14 @@ class Theme(ObjectBase):
def __init__(self, path):
ObjectBase.__init__(self,
path = path,
renderer = None,
provider = Gtk.CssProvider(),
name = None,
author = None,
url = None,
license = None,
entry = None,
base = None
)
self.renderer = Template(
autoescape = False,
context = self.handle_template_context,
search = [path],
global_vars = {
'len': len,
'str': str,
'int': int,
'float': float,
'bool': bool,
'app': self,
'var': var,
'version': version,
'swname': swname,
}
base = None,
hash = None
)
self.load_manifest()
@ -225,12 +270,11 @@ class Theme(ObjectBase):
if not self.provider.to_string():
self.load()
return self.provider()
return self.provider
def load(self):
data = self.renderer.render(self.entry)
self.provider.load_from_data(data.encode('utf-8'))
self.provider.load_from_path(self.path.join(self.entry))
def load_manifest(self):
@ -260,16 +304,14 @@ class Theme(ObjectBase):
if not self.entry:
raise ValueError(f'No entry css specified for theme: {self.name}')
value = self.name + self.author + self.entry
self.hash = hashlib.sha256(value.encode('utf-8')).hexdigest()
def handle_parsing_error(self, provider, section, error):
logging.error(f'[{self.name}] {error.message} {section.get_start_line()}')
def handle_template_context(self, context):
context['theme'] = self
return context
class WatcherBase(ComponentBase, FileSystemEventHandler):
def __init__(self, themes):
FileSystemEventHandler.__init__(self)
@ -292,13 +334,17 @@ class WatcherBase(ComponentBase, FileSystemEventHandler):
def handle_theme_modified(self, theme, path):
if path.name == 'manifest.ini':
theme.load_manifest()
logging.verbose(f'Reloaded manifest for theme: {theme.name}')
try:
if path.name == 'manifest.ini':
theme.load_manifest()
logging.verbose(f'Reloaded manifest for theme: {theme.name}')
elif path.suffix in ['css', 'svg', 'png', 'gif', 'jpg']:
theme.load()
logging.verbose(f'Reloaded theme: {theme.name}')
elif path.suffix in ['css', 'svg', 'png', 'gif', 'jpg']:
theme.load()
logging.verbose(f'Reloaded theme: {theme.name}')
except:
traceback.print_exc()
class MainWatchHandler(WatcherBase):
@ -307,13 +353,23 @@ class MainWatchHandler(WatcherBase):
if path.name == 'main.css':
logging.verbose('Reloading main css')
self.themes.load_main()
try:
self.themes.load_main()
except:
traceback.print_exc()
class SystemWatchHandler(WatcherBase):
def on_modified(self, event):
path = Path(event.src_path)
theme = self.themes.get_theme_by_file(path, system=True)
try:
theme = self.themes.get_theme_by_file(path, system=True)
except:
traceback.print_exc()
return
self.handle_theme_modified(theme, path)
@ -349,6 +405,6 @@ class UserWatchHandler(WatcherBase):
if path.name == 'manifest.ini' and self.userpath.join(path.parent).isdir():
theme = Theme(path.parent)
self.themes.user[theme.path] = theme
self.themes.user[theme.hash] = theme
logging.verbose(f'Created new theme: {theme.name}')