diff --git a/ks_includes/KlippyRest.py b/ks_includes/KlippyRest.py index 822a9886..6dfa31e2 100644 --- a/ks_includes/KlippyRest.py +++ b/ks_includes/KlippyRest.py @@ -35,18 +35,19 @@ class KlippyRest: def get_thumbnail_stream(self, thumbnail): return self.send_request(f"server/files/gcodes/{thumbnail}", json=False) - def send_request(self, method, json=True): + def _do_request(self, method, request_method, data=None, json=None, json_response=True): url = f"{self.endpoint}/{method}" headers = {} if self.api_key is False else {"x-api-key": self.api_key} - data = False + response_data = False try: - response = requests.get(url, headers=headers, timeout=3) + callee = getattr(requests, request_method) + response = callee(url, json=json, data=data, headers=headers, timeout=3) response.raise_for_status() - if json: + if json_response: logging.debug(f"Sending request to {url}") - data = response.json() + response_data = response.json() else: - data = response.content + response_data = response.content except requests.exceptions.HTTPError as h: self.status = self.format_status(h) except requests.exceptions.ConnectionError as c: @@ -59,11 +60,17 @@ class KlippyRest: self.status = self.format_status(r) except Exception as e: self.status = self.format_status(e) - if data: + if response_data: self.status = '' else: logging.error(self.status.replace('\n', '>>')) - return data + return response_data + + def post_request(self, method, data=None, json=None, json_response=True): + return self._do_request(method, "post", data, json, json_response) + + def send_request(self, method, json=True): + return self._do_request(method, "get", json_response=json) @staticmethod def format_status(status): diff --git a/ks_includes/config.py b/ks_includes/config.py index 21878ce0..83fe95d2 100644 --- a/ks_includes/config.py +++ b/ks_includes/config.py @@ -173,7 +173,7 @@ class KlipperScreenConfig: strs = ( 'moonraker_api_key', 'moonraker_host', 'titlebar_name_type', 'screw_positions', 'power_devices', 'titlebar_items', 'z_babystep_values', - 'extrude_distances', "extrude_speeds", + 'extrude_distances', "extrude_speeds", "camera_url", "spoolman", ) numbers = ( 'moonraker_port', 'move_speed_xy', 'move_speed_z', @@ -188,7 +188,9 @@ class KlipperScreenConfig: # This section may be deprecated in favor of moving this options under the printer section numbers = ('rotation', '') strs = ('screw_positions', '') - elif section.startswith('graph') or section.startswith('displayed_macros'): + elif section.startswith('graph')\ + or section.startswith('displayed_macros')\ + or section.startswith('spoolman'): bools = [f'{option}' for option in config[section]] elif section.startswith('z_calibrate_position'): # This section may be deprecated in favor of moving this options under the printer section @@ -487,6 +489,7 @@ class KlipperScreenConfig: extra_sections = [i for i in self.config.sections() if i.startswith("displayed_macros")] extra_sections.extend([i for i in self.config.sections() if i.startswith("graph")]) + extra_sections.extend([i for i in self.config.sections() if i.startswith("spoolman")]) for section in extra_sections: for item in self.config.options(section): value = self.config[section].getboolean(item, fallback=True) diff --git a/ks_includes/defaults.conf b/ks_includes/defaults.conf index 64210b83..fd998299 100644 --- a/ks_includes/defaults.conf +++ b/ks_includes/defaults.conf @@ -281,3 +281,15 @@ enable: {{ moonraker_connected }} name: KlipperScreen icon: settings panel: settings + +[menu __main more spoolman] +name: Spoolman +icon: spoolman +panel: spoolman +enable: {{ printer.spoolman }} + +[menu __print spoolman] +name: Spoolman +icon: spoolman +panel: spoolman +enable: {{ printer.spoolman }} diff --git a/ks_includes/printer.py b/ks_includes/printer.py index a803e712..17398181 100644 --- a/ks_includes/printer.py +++ b/ks_includes/printer.py @@ -26,6 +26,7 @@ class Printer: self.tempstore_size = 1200 self.cameras = [] self.available_commands = {} + self.spoolman = False def reinit(self, printer_info, data): self.config = data['configfile']['config'] @@ -37,6 +38,7 @@ class Printer: self.fancount = 0 self.output_pin_count = 0 self.tempstore = {} + self.spoolman = False self.busy = False if not self.store_timeout: self.store_timeout = GLib.timeout_add_seconds(1, self._update_temp_store) @@ -236,6 +238,7 @@ class Printer: "pause_resume": {"is_paused": self.state == "paused"}, "power_devices": {"count": len(self.get_power_devices())}, "cameras": {"count": len(self.cameras)}, + "spoolman": self.spoolman } } @@ -364,3 +367,7 @@ class Printer: temp = 0 self.tempstore[device][x].append(temp) return True + + def enable_spoolman(self): + logging.info("Enabling Spoolman") + self.spoolman = True diff --git a/panels/extrude.py b/panels/extrude.py index ba668737..67283c3d 100644 --- a/panels/extrude.py +++ b/panels/extrude.py @@ -39,6 +39,7 @@ class Panel(ScreenPanel): 'unload': self._gtk.Button("arrow-up", _("Unload"), "color2"), 'retract': self._gtk.Button("retract", _("Retract"), "color1"), 'temperature': self._gtk.Button("heat-up", _("Temperature"), "color4"), + 'spoolman': self._gtk.Button("spoolman", "Spoolman", "color3"), } self.buttons['extrude'].connect("clicked", self.extrude, "+") self.buttons['load'].connect("clicked", self.load_unload, "+") @@ -48,7 +49,10 @@ class Panel(ScreenPanel): "name": "Temperature", "panel": "temperature" }) - + self.buttons['spoolman'].connect("clicked", self.menu_item_clicked, { + "name": "Spoolman", + "panel": "spoolman" + }) extgrid = self._gtk.HomogeneousGrid() limit = 5 i = 0 @@ -66,6 +70,8 @@ class Panel(ScreenPanel): i += 1 if i < (limit - 1): extgrid.attach(self.buttons['temperature'], i + 1, 0, 1, 1) + if i < (limit - 2) and self._printer.spoolman: + extgrid.attach(self.buttons['spoolman'], i + 2, 0, 1, 1) distgrid = Gtk.Grid() for j, i in enumerate(self.distances): @@ -163,7 +169,7 @@ class Panel(ScreenPanel): def enable_buttons(self, enable): for button in self.buttons: - if button == "temperature": + if button in ("temperature", "spoolman"): continue self.buttons[button].set_sensitive(enable) diff --git a/panels/spoolman.py b/panels/spoolman.py new file mode 100644 index 00000000..6bb60eb8 --- /dev/null +++ b/panels/spoolman.py @@ -0,0 +1,416 @@ +import os.path +import pathlib +import logging +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GdkPixbuf, GObject, Pango, Gdk +from ks_includes.screen_panel import ScreenPanel +from ks_includes.KlippyRest import KlippyRest +from datetime import datetime + +try: + from zoneinfo import ZoneInfo +except ImportError: + from backports.zoneinfo import ZoneInfo + + +def format_date(date): + try: + return datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%f').replace(tzinfo=ZoneInfo('UTC')) + except ValueError: + try: + return datetime.strptime(date, '%Y-%m-%dT%H:%M:%S').replace(tzinfo=ZoneInfo('UTC')) + except ValueError: + return None + + +class SpoolmanVendor: + id: int + name: str + registered: datetime = None + + def __init__(self, **entries): + self.__dict__.update(entries) + for date in ["registered"]: + if date in entries: + self.__setattr__(date, format_date(entries[date])) + + +class SpoolmanFilament: + article_number: str + color_hex: str + comment: str + density: float + diameter: float + id: int + material: str + name: str + price: float + registered: datetime = None + settings_bed_temp: int + settings_extruder_temp: int + spool_weight: float + vendor: SpoolmanVendor = None + weight: float + + def __init__(self, **entries): + self.__dict__.update(entries) + if "vendor" in entries: + self.vendor = SpoolmanVendor(**(entries["vendor"])) + for date in ["registered"]: + if date in entries: + self.__setattr__(date, format_date(entries[date])) + + +class SpoolmanSpool(GObject.GObject): + archived: bool + id: int + remaining_length: float + remaining_weight: float + used_length: float + used_weight: float + lot_nr: str + filament: SpoolmanFilament = None + first_used: datetime = None + last_used: datetime = None + registered: datetime = None + _icon: Gtk.Image = None + theme_path: str = None + _spool_icon: str = None + + def __init__(self, **entries): + GObject.GObject.__init__(self) + self.__dict__.update(entries) + if "filament" in entries: + self.filament = SpoolmanFilament(**(entries["filament"])) + for date in ["first_used", "last_used", "registered"]: + if date in entries: + self.__setattr__(date, format_date(entries[date])) + + @property + def name(self): + result = self.filament.name + if self.filament.vendor: + result = " ".join([self.filament.vendor.name, "-", result]) + return result + + @property + def icon(self): + if self._icon is None: + if SpoolmanSpool._spool_icon is None: + klipperscreendir = pathlib.Path(__file__).parent.resolve().parent + _spool_icon_path = os.path.join( + klipperscreendir, "styles", SpoolmanSpool.theme_path, "images", "spool.svg" + ) + if not os.path.isfile(_spool_icon_path): + _spool_icon_path = os.path.join(klipperscreendir, "styles", "spool.svg") + SpoolmanSpool._spool_icon = pathlib.Path(_spool_icon_path).read_text() + + loader = GdkPixbuf.PixbufLoader() + loader.write( + SpoolmanSpool._spool_icon.replace('var(--filament-color)', f'#{self.filament.color_hex}').encode() + ) + loader.close() + self._icon = loader.get_pixbuf() + return self._icon + + +class Panel(ScreenPanel): + apiClient: KlippyRest + _active_spool_id: int = None + + @staticmethod + def spool_compare_id(model, row1, row2, user_data): + spool1 = model.get_value(row1, 0) + spool2 = model.get_value(row2, 0) + return spool1.id - spool2.id + + @staticmethod + def spool_compare_date(model, row1, row2, user_data): + spool1 = model.get_value(row1, 0) + spool2 = model.get_value(row2, 0) + return 1 if (spool1.last_used or datetime.min).replace(tzinfo=None) > \ + (spool2.last_used or datetime.min).replace(tzinfo=None) else -1 + + def _on_material_filter_changed(self, sender): + treeiter = sender.get_active_iter() + if treeiter is not None: + model = sender.get_model() + self._filters["material"] = model[treeiter][0] + self._filterable.refilter() + + def __init__(self, screen, title): + super().__init__(screen, title) + self.apiClient = screen.apiclient + if self._config.get_main_config().getboolean("24htime", True): + self.timeFormat = '%Y-%m-%d %H:%M' + else: + self.timeFormat = '%Y-%m-%d %I:%M %p' + + SpoolmanSpool.theme_path = screen.theme + GObject.type_register(SpoolmanSpool) + self._filters = {} + self._model = Gtk.TreeStore(SpoolmanSpool.__gtype__) + self._materials = Gtk.ListStore(str, str) + + self._filterable = self._model.filter_new() + self._filterable.set_visible_func(self._filter_spools) + + sortable = Gtk.TreeModelSort(self._filterable) + sortable.set_sort_func(0, self.spool_compare_id) + sortable.set_sort_func(1, self.spool_compare_date) + + self.scroll = self._gtk.ScrolledWindow() + + sbox = Gtk.Box(spacing=0) + sbox.set_vexpand(False) + + clear_active_spool = self._gtk.Button("cancel", _("Clear"), "color2", self.bts, Gtk.PositionType.LEFT, 1) + clear_active_spool.get_style_context().add_class("buttons_slim") + clear_active_spool.connect('clicked', self.clear_active_spool) + + refresh = self._gtk.Button("refresh", style="color1", scale=.66) + refresh.get_style_context().add_class("buttons_slim") + refresh.connect('clicked', self.load_spools) + + sort_btn_id = self._gtk.Button(None, _("ID"), "color4", self.bts, Gtk.PositionType.RIGHT, 1) + sort_btn_id.connect("clicked", self.change_sort, "id") + sort_btn_id.get_style_context().add_class("buttons_slim") + + sort_btn_used = self._gtk.Button(None, _("Last Used"), "color3", self.bts, Gtk.PositionType.RIGHT, 1) + sort_btn_used.connect("clicked", self.change_sort, "last_used") + sort_btn_used.get_style_context().add_class("buttons_slim") + + switch = Gtk.Switch() + switch.set_hexpand(False) + switch.set_vexpand(False) + switch.set_active(self._config.get_config().getboolean("spoolman", "hide_archived", fallback=True)) + switch.connect("notify::active", self.switch_config_option, "spoolman", "hide_archived", self.load_spools) + + name = Gtk.Label() + name.set_markup(_("Archived")) + name.set_halign(Gtk.Align.START) + name.set_valign(Gtk.Align.CENTER) + name.set_line_wrap(True) + name.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) + + archived = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + archived.set_valign(Gtk.Align.CENTER) + archived.add(name) + archived.add(switch) + + sbox.pack_start(sort_btn_id, True, True, 0) + sbox.pack_start(sort_btn_used, True, True, 0) + sbox.pack_start(clear_active_spool, True, True, 0) + sbox.pack_start(refresh, True, True, 0) + sbox.pack_start(archived, False, False, 5) + sbox.set_hexpand(True) + sbox.set_vexpand(False) + + filter_box = Gtk.ListBox() + filter_box.set_selection_mode(Gtk.SelectionMode.NONE) + _filter = Gtk.Expander(label=_("Filter")) + _filter.add(filter_box) + + row = Gtk.ListBoxRow() + hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + row.add(hbox) + + label = Gtk.Label(_("Material")) + _material_filter = Gtk.ComboBox() + _material_filter.set_model(self._materials) + _material_filter.connect("changed", self._on_material_filter_changed) + cellrenderertext = Gtk.CellRendererText() + _material_filter.pack_start(cellrenderertext, True) + _material_filter.add_attribute(cellrenderertext, "text", 1) + + hbox.pack_start(label, False, True, 0) + hbox.pack_start(_material_filter, True, True, 0) + + filter_box.add(row) + + self.main = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + self.main.set_vexpand(True) + self.main.pack_start(sbox, False, False, 0) + self.main.pack_start(_filter, False, True, 0) + self.main.pack_start(self.scroll, True, True, 0) + + self.load_spools() + self.get_active_spool() + self._treeview = Gtk.TreeView(model=sortable) + self._treeview.set_headers_visible(False) + self._treeview.set_show_expanders(False) + + text_renderer = Gtk.CellRendererText() + pixbuf_renderer = Gtk.CellRendererPixbuf() + pixbuf_renderer.set_padding(5, 5) + checkbox_renderer = Gtk.CellRendererToggle() + column_id = Gtk.TreeViewColumn(cell_renderer=text_renderer) + column_id.set_cell_data_func( + text_renderer, + lambda column, cell, model, it, data: + self._set_cell_background(cell, model.get_value(it, 0)) and + cell.set_property('text', f'{model.get_value(it, 0).id}') + ) + column_id.set_sort_column_id(0) + + column_icon = Gtk.TreeViewColumn(cell_renderer=pixbuf_renderer) + column_icon.set_cell_data_func( + pixbuf_renderer, + lambda column, cell, model, it, data: + self._set_cell_background(cell, model.get_value(it, 0)) and + cell.set_property('pixbuf', model.get_value(it, 0).icon) + ) + + column_spool = Gtk.TreeViewColumn(cell_renderer=text_renderer) + column_spool.set_expand(True) + column_spool.set_cell_data_func( + text_renderer, + lambda column, cell, model, it, data: + self._set_cell_background(cell, model.get_value(it, 0)) and + cell.set_property('markup', self._get_filament_formated(model.get_value(it, 0))) + ) + + column_last_used = Gtk.TreeViewColumn(cell_renderer=text_renderer) + column_last_used.set_visible(False) + column_last_used.set_sort_column_id(1) + + column_material = Gtk.TreeViewColumn(cell_renderer=text_renderer) + column_material.set_cell_data_func( + text_renderer, + lambda column, cell, model, it, data: + self._set_cell_background(cell, model.get_value(it, 0)) and + cell.set_property('text', model.get_value(it, 0).filament.material) + ) + + checkbox_renderer.connect("toggled", self._set_active_spool) + column_toggle_active_spool = Gtk.TreeViewColumn(cell_renderer=checkbox_renderer) + column_toggle_active_spool.set_cell_data_func( + checkbox_renderer, + lambda column, cell, model, it, data: + self._set_cell_background(cell, model.get_value(it, 0)) and + cell.set_property('active', model.get_value(it, 0).id == self._active_spool_id) + ) + + self._treeview.append_column(column_id) + self._treeview.append_column(column_icon) + self._treeview.append_column(column_spool) + self._treeview.append_column(column_last_used) + self._treeview.append_column(column_material) + self._treeview.append_column(column_toggle_active_spool) + + self.current_sort_widget = sort_btn_id + sort_btn_used.clicked() + + self.scroll.add(self._treeview) + self.content.add(self.main) + + def _filter_spools(self, model, i, data): + spool: SpoolmanSpool = model[i][0] + matches = True + if ("material" in self._filters) and (self._filters["material"] is not None): + matches &= spool.filament.material == self._filters["material"] + return matches + + def _set_cell_background(self, cell, spool: SpoolmanSpool): + cell.set_property('cell-background-rgba', Gdk.RGBA(1, 1, 1, .1) if spool.id == self._active_spool_id else None) + return True + + def _get_filament_formated(self, spool: SpoolmanSpool): + if spool.id == self._active_spool_id: + result = f'{spool.name}\n' + else: + result = f'{spool.name}\n' + if spool.last_used: + result += f'{_("Last used")}: {spool.last_used.astimezone():{self.timeFormat}}\n' + if hasattr(spool, "remaining_weight"): + result += f'{_("Remaining weight")}: {round(spool.remaining_weight, 2)} g\n' + if hasattr(spool, "remaining_length"): + result += f'{_("Remaining length")}: {round(spool.remaining_length / 1000, 2)} m\n' + + return result.strip() + + def _set_active_spool(self, sender, path): + model = self._treeview.get_model() + it = model.get_iter(path) + spool = model.get_value(it, 0) + if spool.id == self._active_spool_id: + self.clear_active_spool() + else: + self.set_active_spool(spool) + + def change_sort(self, widget, sort_type): + self.current_sort_widget.set_image(None) + self.current_sort_widget = widget + if sort_type == "id": + logging.info("Sorting by ID") + column = 0 + elif sort_type == "last_used": + logging.info("Sorting by Last Used") + column = 1 + else: + logging.error("Unknown sort type") + return + if self._treeview.get_column(column).get_sort_order() == Gtk.SortType.DESCENDING: + new_sort_order = Gtk.SortType.ASCENDING + else: + new_sort_order = Gtk.SortType.DESCENDING + self._treeview.get_column(column).set_sort_order(new_sort_order) + self._treeview.get_model().set_sort_column_id(column, new_sort_order) + icon = "arrow-down" if new_sort_order == Gtk.SortType.DESCENDING else "arrow-up" + widget.set_image(self._gtk.Image(icon, self._gtk.img_scale * self.bts)) + + def process_update(self, action, data): + if action == "notify_active_spool_set": + self._active_spool_id = data['spool_id'] + self._treeview.get_model().foreach(lambda store, treepath, treeiter: + store.row_changed(treepath, treeiter) + ) + self._treeview.queue_draw() + + def load_spools(self, data=None): + hide_archived = self._config.get_config().getboolean("spoolman", "hide_archived", fallback=True) + self._model.clear() + self._materials.clear() + spools = self.apiClient.post_request("server/spoolman/proxy", json={ + "request_method": "GET", + "path": f"/v1/spool?allow_archived={not hide_archived}", + }) + if not spools or "result" not in spools: + self._screen.show_error_modal("Exception when trying to fetch spools") + return + + materials = [] + for spool in spools["result"]: + spoolObject = SpoolmanSpool(**spool) + self._model.append(None, [spoolObject]) + if spoolObject.filament.material not in materials: + materials.append(spoolObject.filament.material) + + materials.sort() + self._materials.append([None, _("All")]) + for material in materials: + self._materials.append([material, material]) + + def clear_active_spool(self, sender: Gtk.Button = None): + result = self.apiClient.post_request("server/spoolman/spool_id", json={}) + if not result: + self._screen.show_error_modal("Exception when setting active spool") + return + + def set_active_spool(self, spool: SpoolmanSpool): + result = self.apiClient.post_request("server/spoolman/spool_id", json={ + "spool_id": spool.id + }) + if not result: + self._screen.show_error_modal("Exception when setting active spool") + return + + def get_active_spool(self) -> SpoolmanSpool: + result = self.apiClient.send_request("server/spoolman/spool_id") + if not result: + self._screen.show_error_modal("Exception when getting active spool") + return + self._active_spool_id = result["result"]["spool_id"] + return self._active_spool_id diff --git a/screen.py b/screen.py index bdb39646..cbec0e6f 100755 --- a/screen.py +++ b/screen.py @@ -894,6 +894,8 @@ class KlipperScreen(Gtk.Window): self.files.initialize() self.files.refresh_files() + self.init_spoolman() + logging.info("Printer initialized") self.initialized = True self.reinit_count = 0 @@ -919,6 +921,18 @@ class KlipperScreen(Gtk.Window): logging.info(f"Temperature store size: {self.printer.tempstore_size}") except KeyError: logging.error("Couldn't get the temperature store size") + return False + + def init_spoolman(self): + server_config = self.apiclient.send_request("server/config") + if server_config: + try: + server_config["result"]["config"]["spoolman"] + self.printer.enable_spoolman() + except KeyError: + logging.warning("Not using Spoolman") + + return False def show_keyboard(self, entry=None, event=None): if self.keyboard is not None: diff --git a/scripts/KlipperScreen-requirements.txt b/scripts/KlipperScreen-requirements.txt index 3acf4009..4985848e 100644 --- a/scripts/KlipperScreen-requirements.txt +++ b/scripts/KlipperScreen-requirements.txt @@ -7,4 +7,5 @@ PyGObject==3.44.1 python-mpv==0.5.2;python_version<"3.10" python-mpv==1.0.4;python_version>="3.10" six==1.16.0 -dbus-python==1.3.2 \ No newline at end of file +dbus-python==1.3.2 +backports.zoneinfo;python_version<"3.9" diff --git a/styles/base.css b/styles/base.css index 552152a0..fe35c096 100644 --- a/styles/base.css +++ b/styles/base.css @@ -11,11 +11,41 @@ button:disabled { opacity: .2; } +list row, +treeview.view, window { background-color: #13181C; -gtk-icon-shadow: none; } +switch slider { + border: 0; +} + +switch, treeview.view check { + margin: 0.75em; + min-width: 6em; + min-height: 3em; + border: 0; + border-radius: 3em; +} + +treeview.view check { + background-color: rgb(32,41,47); + -gtk-icon-source: -gtk-icontheme("switch-off-symbolic"); + background-repeat: no-repeat; + background-position: left center; + background-size : 3em 3em; + background-image: image(url("./styles/circle.svg")); + -gtk-icon-transform: translateX(1.5em); +} +treeview.view check:checked { + background-color : rgb(53,132,228); + background-position: right center; + -gtk-icon-source: -gtk-icontheme("switch-on-symbolic"); + -gtk-icon-transform: translateX(-1.5em); +} + button { background-image: none; background-color: #13181C; @@ -110,18 +140,6 @@ combobox arrow { min-width: 1em; } -switch { - margin: 0.75em; - min-width: 6em; - min-height: 3em; - border: 0; - border-radius: 3em; -} - -switch slider { - border: 0; -} - entry { font-size: 1em; background-color: #20292F; diff --git a/styles/circle.svg b/styles/circle.svg new file mode 100644 index 00000000..14c0d218 --- /dev/null +++ b/styles/circle.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/styles/colorized/images/spool.svg b/styles/colorized/images/spool.svg new file mode 100644 index 00000000..32a94d22 --- /dev/null +++ b/styles/colorized/images/spool.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/styles/colorized/images/spoolman.svg b/styles/colorized/images/spoolman.svg new file mode 100644 index 00000000..45aa41cb --- /dev/null +++ b/styles/colorized/images/spoolman.svg @@ -0,0 +1,90 @@ + + +Created with Fabric.js 5.3.0 + + + + + + + + + + + diff --git a/styles/colorized/style.css b/styles/colorized/style.css index 744ef1a7..156f5c8a 100644 --- a/styles/colorized/style.css +++ b/styles/colorized/style.css @@ -5,6 +5,8 @@ color: #fdf6e3; /*base3*/ } +list row, +treeview.view, window { background-color: #002b36; /*base03*/ } @@ -78,10 +80,12 @@ combobox box button { border-color: #002b36; /*base03*/ } +treeview.view check, switch { background-color: #073642; /* base02 */ } +treeview.view check:checked, switch:checked { background-color: #859900; /*solarized-green*/ } diff --git a/styles/material-dark/images/spool.svg b/styles/material-dark/images/spool.svg new file mode 100644 index 00000000..3b54bf9f --- /dev/null +++ b/styles/material-dark/images/spool.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/styles/material-dark/images/spoolman.svg b/styles/material-dark/images/spoolman.svg new file mode 100644 index 00000000..2825a532 --- /dev/null +++ b/styles/material-dark/images/spoolman.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + diff --git a/styles/material-dark/style.css b/styles/material-dark/style.css index b37f900b..3b750564 100644 --- a/styles/material-dark/style.css +++ b/styles/material-dark/style.css @@ -4,6 +4,8 @@ color: #e2e2e2; } +list row, +treeview.view, window { background-color: #121212; } @@ -75,10 +77,12 @@ combobox box button { border-color: #121212; } +treeview.view check, switch { background-color: #1f1f1f; } +treeview.view check:checked, switch:checked { background-color: #849900; } diff --git a/styles/material-darker/images/spool.svg b/styles/material-darker/images/spool.svg new file mode 100644 index 00000000..3b54bf9f --- /dev/null +++ b/styles/material-darker/images/spool.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/styles/material-darker/images/spoolman.svg b/styles/material-darker/images/spoolman.svg new file mode 100644 index 00000000..2825a532 --- /dev/null +++ b/styles/material-darker/images/spoolman.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + diff --git a/styles/material-darker/style.css b/styles/material-darker/style.css index d230469d..c5d94195 100644 --- a/styles/material-darker/style.css +++ b/styles/material-darker/style.css @@ -5,6 +5,8 @@ font-family: RobotoMedium; } +list row, +treeview.view, window { background-color: #121212; } @@ -59,10 +61,12 @@ combobox box button { border-color: #121212; } +treeview.view check, switch { background-color: #1c1c1c; } +treeview.view check:checked, switch:checked { background-color: #2f5631; } diff --git a/styles/material-light/images/spool.svg b/styles/material-light/images/spool.svg new file mode 100644 index 00000000..cdc8f4f4 --- /dev/null +++ b/styles/material-light/images/spool.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/styles/material-light/images/spoolman.svg b/styles/material-light/images/spoolman.svg new file mode 100644 index 00000000..5f420ce0 --- /dev/null +++ b/styles/material-light/images/spoolman.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + diff --git a/styles/material-light/style.css b/styles/material-light/style.css index 2a6c5824..1e33d2f2 100644 --- a/styles/material-light/style.css +++ b/styles/material-light/style.css @@ -4,6 +4,8 @@ color: black; } +list row, +treeview.view, window { background-color: #FAFAFA; } @@ -88,10 +90,12 @@ combobox box button { border-color: #BDBDBD; } +treeview.view check, switch { background-color: #BDBDBD; } +treeview.view check:checked, switch:checked { background-color: #C5E1A5; } diff --git a/styles/spool.svg b/styles/spool.svg new file mode 100644 index 00000000..66eb18cb --- /dev/null +++ b/styles/spool.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/styles/z-bolt/images/spool.svg b/styles/z-bolt/images/spool.svg new file mode 100644 index 00000000..c42db8fe --- /dev/null +++ b/styles/z-bolt/images/spool.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/styles/z-bolt/images/spoolman.svg b/styles/z-bolt/images/spoolman.svg new file mode 100644 index 00000000..0f7246df --- /dev/null +++ b/styles/z-bolt/images/spoolman.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + diff --git a/styles/z-bolt/style.css b/styles/z-bolt/style.css index d3de542d..dcf1ab44 100644 --- a/styles/z-bolt/style.css +++ b/styles/z-bolt/style.css @@ -2,6 +2,8 @@ color: white; } +list row, +treeview.view, window { background-color: #13181C; } @@ -53,10 +55,11 @@ combobox box button { border-color: #cccccc; } +treeview.view check, switch { background-color: #20292F; } - +treeview.view check:checked, switch:checked { background-color: #3584e4; }