diff --git a/data/io.github.fsobolev.Cavalier.gschema.xml b/data/io.github.fsobolev.Cavalier.gschema.xml index 8943234..78985fe 100644 --- a/data/io.github.fsobolev.Cavalier.gschema.xml +++ b/data/io.github.fsobolev.Cavalier.gschema.xml @@ -25,7 +25,6 @@ Defines what the visualizer looks like. - @@ -47,16 +46,21 @@ Roundness of items - This setting only affects "levels" and "particles" modes. + This setting affects "levels", "particles" and "spine" modes. 10 Thickness of lines - Thickness of lines in "line" mode (in pixels). + Thickness of lines when filling is off (in pixels). 15 + + Filling + Whether shapes should be filled or outlined. + true + Number of bars Number of bars in CAVA config diff --git a/src/draw_functions.py b/src/draw_functions.py index 5b964ae..bf6ea3d 100644 --- a/src/draw_functions.py +++ b/src/draw_functions.py @@ -58,7 +58,7 @@ def draw_element(cr, x, y, width, height, radius): radius * min(width, height) / 100, 90 * degrees, -180 * degrees) cr.close_path() -def wave(sample, cr, width, height, colors): +def wave(sample, cr, width, height, colors, fill, thickness): set_source(cr, height, colors) ls = len(sample) cr.move_to(0, (1.0 - sample[0]) * height) @@ -67,76 +67,99 @@ def wave(sample, cr, width, height, colors): cr.rel_curve_to(width / (ls - 1) * 0.5, 0.0, \ width / (ls - 1) * 0.5, height_diff * height, \ width / (ls - 1), height_diff * height) - cr.line_to(width, height) - cr.line_to(0, height) - cr.fill() - -def line(sample, cr, width, height, colors, thickness): - set_source(cr, height, colors) - ls = len(sample) - cr.move_to(0, (1.0 - sample[0]) * height) - cr.set_line_width(thickness) - for i in range(ls - 1): - height_diff = (sample[i] - sample[i+1]) - cr.rel_curve_to(width / (ls - 1) * 0.5, 0.0, \ - width / (ls - 1) * 0.5, height_diff * height, \ - width / (ls - 1), height_diff * height) - cr.stroke() + if fill: + cr.line_to(width, height) + cr.line_to(0, height) + cr.fill() + else: + cr.set_line_width(thickness) + cr.stroke() -def levels(sample, cr, width, height, colors, offset, radius): +def levels(sample, cr, width, height, colors, offset, radius, fill, thickness): set_source(cr, height, colors) ls = len(sample) step = width / ls + offset_px = step * offset / 100 + if fill: + thickness = 0 + else: + thickness = min(thickness, \ + (step - offset_px * 2) / 2, + (height / 10 - offset_px) / 2) + cr.set_line_width(thickness) for i in range(ls): q = int(round(sample[i], 1) * 10) for r in range(q): - draw_element(cr, step * i + step * offset / 100, \ - height - (height / 10 * (r + 1)) * (1 - offset / 400), \ - max(step - step * offset / 100 * 2, 1), \ - max(height / 10 * (1 - offset / 50), 1), radius) - cr.fill() + draw_element(cr, step * i + offset_px + thickness / 2, \ + height - (height / 10 * (r + 1)) + offset_px / 2 + thickness / 2, \ + max(step - offset_px * 2 - thickness, 1), \ + max(height / 10 - offset_px - thickness, 1), radius) + cr.fill() if fill else cr.stroke() -def particles(sample, cr, width, height, colors, offset, radius): +def particles(sample, cr, width, height, colors, offset, radius, fill, thickness): set_source(cr, height, colors) ls = len(sample) step = width / ls + offset_px = step * offset / 100 + if fill: + thickness = 0 + else: + thickness = min(thickness, \ + (step - offset_px * 2) / 2, + (height / 10) / 2) + cr.set_line_width(thickness) for i in range(ls): - draw_element(cr, step * i + step * offset / 100, \ - height * 0.9 - height * 0.9 * sample[i], \ - max(step - step * offset / 100 * 2, 1), \ - max(height / 10, 1), radius) - cr.fill() + draw_element(cr, step * i + offset_px + thickness / 2, \ + height * 0.9 - height * 0.9 * sample[i] + thickness / 2, \ + max(step - offset_px * 2 - thickness, 1), \ + max(height / 10 - thickness, 1), radius) + cr.fill() if fill else cr.stroke() -def spine(sample, cr, width, height, colors, offset, radius): +def spine(sample, cr, width, height, colors, offset, radius, fill, thickness): ls = len(sample) + cr.set_line_width(thickness) if height > width: step = height / ls for i in range(ls): set_source(cr, height, colors, sample[i] - (0.95 - i / ls)) offset_px = step * offset / 100 * sample[i] - draw_element(cr, width / 2 - sample[i] * step / 2 + offset_px, \ - step * i + step / 2 - sample[i] * step / 2 + offset_px, \ - step * sample[i] - offset_px * 2, \ - step * sample[i] - offset_px * 2, radius) - cr.fill() + if fill: + thickness = 0 + else: + thickness = min(thickness, \ + (step * sample[i] - offset_px * 2) / 2) + cr.set_line_width(thickness) + draw_element(cr, width / 2 - sample[i] * step / 2 + offset_px + thickness / 2, \ + step * i + step / 2 - sample[i] * step / 2 + offset_px + thickness / 2, \ + step * sample[i] - offset_px * 2 - thickness, \ + step * sample[i] - offset_px * 2 - thickness, radius) + cr.fill() if fill else cr.stroke() else: step = width / ls for i in range(ls): set_source(cr, height, colors, sample[i] - 0.45) offset_px = step * offset / 100 * sample[i] + if not fill: + offset_px += thickness / 2 draw_element(cr, \ step * i + step / 2 - sample[i] * step / 2 + offset_px, \ height / 2 - sample[i] * step / 2 + offset_px, \ step * sample[i] - offset_px * 2, \ step * sample[i] - offset_px * 2, radius) - cr.fill() + cr.fill() if fill else cr.stroke() -def bars(sample, cr, width, height, colors, offset): +def bars(sample, cr, width, height, colors, offset, fill, thickness): set_source(cr, height, colors) ls = len(sample) step = width / ls offset_px = step * offset / 100 + if fill: + thickness = 0 + else: + thickness = min(thickness, (step - offset_px * 2) / 2) + cr.set_line_width(thickness) for i in range(ls): - cr.rectangle(step * i + offset_px, height - height * sample[i], \ - step - offset_px * 2, height) - cr.fill() + cr.rectangle(step * i + offset_px + thickness / 2, \ + height - height * sample[i] + thickness / 2, \ + max(step - offset_px * 2 - thickness, 1), height * sample[i] - thickness) + cr.fill() if fill else cr.stroke() diff --git a/src/drawing_area.py b/src/drawing_area.py index e134d4a..ade9fae 100644 --- a/src/drawing_area.py +++ b/src/drawing_area.py @@ -31,7 +31,7 @@ from gi.repository import Gtk, GObject from threading import Thread from cavalier.cava import Cava -from cavalier.draw_functions import wave, line, levels, particles, spine, bars +from cavalier.draw_functions import wave, levels, particles, spine, bars from cavalier.settings import CavalierSettings class CavalierDrawingArea(Gtk.DrawingArea): @@ -71,6 +71,7 @@ def on_settings_changed(self, key): self.offset = self.settings['items-offset'] self.roundness = self.settings['items-roundness'] self.thickness = self.settings['line-thickness'] + self.fill = self.settings['fill'] self.reverse_order = self.settings['reverse-order'] self.channels = self.settings['channels'] try: @@ -97,22 +98,20 @@ def on_settings_changed(self, key): def draw_func(self, area, cr, width, height, data, n): if len(self.cava_sample) > 0: if self.draw_mode == 'wave': - wave(self.cava_sample, cr, width, height, self.colors) - elif self.draw_mode == 'line': - line(self.cava_sample, cr, width, height, self.colors, \ - self.thickness) + wave(self.cava_sample, cr, width, height, self.colors, \ + self.fill, self.thickness) elif self.draw_mode == 'levels': levels(self.cava_sample, cr, width, height, self.colors, \ - self.offset, self.roundness) + self.offset, self.roundness, self.fill, self.thickness) elif self.draw_mode == 'particles': particles(self.cava_sample, cr, width, height, self.colors, \ - self.offset, self.roundness) + self.offset, self.roundness, self.fill, self.thickness) elif self.draw_mode == 'spine': spine(self.cava_sample, cr, width, height, self.colors, \ - self.offset, self.roundness) + self.offset, self.roundness, self.fill, self.thickness) elif self.draw_mode == 'bars': bars(self.cava_sample, cr, width, height, self.colors, \ - self.offset) + self.offset, self.fill, self.thickness) def redraw(self): self.queue_draw() diff --git a/src/preferences_window.py b/src/preferences_window.py index 0639a0d..04d98a3 100644 --- a/src/preferences_window.py +++ b/src/preferences_window.py @@ -66,14 +66,6 @@ def create_cavalier_page(self): self.wave_row.set_activatable_widget(self.wave_check_btn) self.cavalier_mode_group.add(self.wave_row) - self.line_row = Adw.ActionRow.new() - self.line_row.set_title(_('Line')) - self.line_check_btn = Gtk.CheckButton.new() - self.line_check_btn.set_group(self.wave_check_btn) - self.line_row.add_prefix(self.line_check_btn) - self.line_row.set_activatable_widget(self.line_check_btn) - self.cavalier_mode_group.add(self.line_row) - self.levels_row = Adw.ActionRow.new() self.levels_row.set_title(_('Levels')) self.levels_check_btn = Gtk.CheckButton.new() @@ -136,7 +128,7 @@ def create_cavalier_page(self): self.pref_roundness = Adw.ActionRow.new() self.pref_roundness.set_title(_('Roundness of items')) self.pref_roundness.set_subtitle( \ - _('This setting only affects "levels" and "particles" modes.\n0 - square, 1 - round')) + _('This setting affects "levels", "particles" and "spine" modes.\n0 - square, 1 - round')) self.pref_roundness_scale = Gtk.Scale.new_with_range( \ Gtk.Orientation.HORIZONTAL, 0.0, 1.0, 0.02) self.pref_roundness_scale.set_size_request(190, -1) @@ -145,10 +137,21 @@ def create_cavalier_page(self): self.pref_roundness.add_suffix(self.pref_roundness_scale) self.cavalier_group.add(self.pref_roundness) + self.pref_fill = Adw.ActionRow.new() + self.pref_fill.set_title(_('Filling')) + self.pref_fill.set_subtitle( \ + _('Whether shapes should be filled or outlined.')) + self.pref_fill_switch = Gtk.Switch.new() + self.pref_fill_switch.set_valign(Gtk.Align.CENTER) + self.pref_fill.add_suffix(self.pref_fill_switch) + self.pref_fill.set_activatable_widget( \ + self.pref_fill_switch) + self.cavalier_group.add(self.pref_fill) + self.pref_thickness = Adw.ActionRow.new() self.pref_thickness.set_title(_('Thickness of lines')) self.pref_thickness.set_subtitle( \ - _('Thickness of lines in "line" mode (in pixels).')) + _('Thickness of lines when filling is off (in pixels).')) self.pref_thickness_scale = Gtk.Scale.new_with_range( \ Gtk.Orientation.HORIZONTAL, 1.0, 40.0, 1.0) self.pref_thickness_scale.set_size_request(180, -1) @@ -392,7 +395,7 @@ def create_colors_page(self): self.bg_color_btns = [] def load_settings(self): - (self.wave_check_btn, self.line_check_btn, self.levels_check_btn, \ + (self.wave_check_btn, self.levels_check_btn, \ self.particles_check_btn, self.spine_check_btn, self.bars_check_btn)[ \ self.settings.get_range('mode')[1].index(self.settings['mode']) \ ].set_active(True) @@ -401,6 +404,7 @@ def load_settings(self): self.pref_roundness_scale.set_value( \ round(self.settings['items-roundness'] / 50.0, 2)) self.pref_thickness_scale.set_value(self.settings['line-thickness']) + self.pref_fill_switch.set_active(self.settings['fill']) self.pref_sharp_corners_switch.set_active( \ self.settings['sharp-corners']) self.pref_show_controls_switch.set_active( \ @@ -447,7 +451,6 @@ def load_settings(self): def bind_settings(self): self.wave_check_btn.connect('toggled', self.change_mode, 'wave') - self.line_check_btn.connect('toggled', self.change_mode, 'line') self.levels_check_btn.connect('toggled', self.change_mode, 'levels') self.particles_check_btn.connect('toggled', self.change_mode, \ 'particles') @@ -464,6 +467,11 @@ def bind_settings(self): 'line-thickness', self.pref_thickness_scale.get_value) # `notify::state` signal returns additional parameter that # we don't need, that's why lambda is used. + self.pref_fill_switch.connect('notify::state', \ + lambda *args : self.save_setting(self.pref_fill_switch, \ + 'fill', self.pref_fill_switch.get_state())) + # `notify::state` signal returns additional parameter that + # we don't need, that's why lambda is used. self.pref_sharp_corners_switch.connect('notify::state', \ lambda *args : self.save_setting(self.pref_sharp_corners_switch, \ 'sharp-corners', self.pref_sharp_corners_switch.get_state())) diff --git a/src/shortcuts.py b/src/shortcuts.py index e1966bc..411d8e9 100644 --- a/src/shortcuts.py +++ b/src/shortcuts.py @@ -110,6 +110,13 @@ def add_shortcuts(widget, settings): Gtk.ShortcutTrigger.parse_string("T"), \ Gtk.NamedAction.new("cavalier.decrease-thickness"))) + act_toggle_fill = Gio.SimpleAction.new("toggle-fill", None) + act_toggle_fill.connect('activate', toggle_setting, settings, 'fill') + action_map.add_action(act_toggle_fill) + shortcut_controller.add_shortcut(Gtk.Shortcut.new( \ + Gtk.ShortcutTrigger.parse_string("F"), \ + Gtk.NamedAction.new("cavalier.toggle-fill"))) + act_toggle_corners = Gio.SimpleAction.new("toggle-corners", None) act_toggle_corners.connect('activate', toggle_setting, settings, \ 'sharp-corners') diff --git a/src/shortcuts_dialog.ui b/src/shortcuts_dialog.ui index 5ab05e8..1921148 100644 --- a/src/shortcuts_dialog.ui +++ b/src/shortcuts_dialog.ui @@ -34,6 +34,12 @@ R <Shift>R + + + Toggle Filling + F + + Toggle Sharp Corners