sidebar: Create a Header class
This will be used to display different types of playlists in the sidebar, such as artist or genre. It also has a revealer that shows its child when the header is active. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
2542a6cbd7
commit
b25ca24dc3
|
@ -45,3 +45,14 @@ listview > row:checked:selected:hover {
|
|||
listview > row:checked:selected:active {
|
||||
background-color: alpha(@accent_color, 0.39);
|
||||
}
|
||||
|
||||
image.emmental-sidebar-arrow {
|
||||
transition: 250ms;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
image.emmental-sidebar-arrow:checked {
|
||||
transition: 250ms;
|
||||
transform: rotate(-180deg);
|
||||
color: @accent_color;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""A sidebar Section Header widget."""
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
from .title import Title
|
||||
|
||||
|
||||
class Header(Gtk.Box):
|
||||
"""The Section Header."""
|
||||
|
||||
icon_name = GObject.Property(type=str)
|
||||
title = GObject.Property(type=str)
|
||||
subtitle = GObject.Property(type=str)
|
||||
pending = GObject.Property(type=bool, default=False)
|
||||
progress = GObject.Property(type=float)
|
||||
|
||||
active = GObject.Property(type=bool, default=False)
|
||||
extra_widget = GObject.Property(type=Gtk.Widget)
|
||||
reveal_widget = GObject.Property(type=Gtk.Widget)
|
||||
animation = GObject.Property(type=Gtk.RevealerTransitionType,
|
||||
default=Gtk.RevealerTransitionType.SLIDE_UP)
|
||||
|
||||
def __init__(self, icon_name: str = "image-missing",
|
||||
title: str = "", subtitle: str = "",
|
||||
extra_widget: Gtk.Widget = None, **kwargs):
|
||||
"""Initialize a row Header widget."""
|
||||
super().__init__(icon_name=icon_name, extra_widget=extra_widget,
|
||||
title=title, subtitle=subtitle,
|
||||
orientation=Gtk.Orientation.VERTICAL, **kwargs)
|
||||
self._overlay = Gtk.Overlay(css_name="button")
|
||||
self._box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=12)
|
||||
self._icon = Gtk.Image(icon_name=self.icon_name,
|
||||
icon_size=Gtk.IconSize.LARGE)
|
||||
self._title = Title(title=self.title, subtitle=self.subtitle)
|
||||
self._extra = Adw.Bin(child=self.extra_widget, valign=Gtk.Align.CENTER)
|
||||
self._arrow = Gtk.Image(icon_name="go-down-symbolic")
|
||||
self._progress = Gtk.ProgressBar(valign=Gtk.Align.END,
|
||||
visible=self.pending)
|
||||
self._revealer = Gtk.Revealer(transition_type=self.animation)
|
||||
self._clicked = Gtk.GestureClick()
|
||||
|
||||
self._arrow.add_css_class("emmental-sidebar-arrow")
|
||||
self._progress.add_css_class("osd")
|
||||
|
||||
self.bind_property("icon-name", self._icon, "icon-name")
|
||||
self.bind_property("title", self._title, "title")
|
||||
self.bind_property("subtitle", self._title, "subtitle")
|
||||
self.bind_property("extra-widget", self._extra, "child")
|
||||
self.bind_property("pending", self._progress, "visible")
|
||||
self.bind_property("progress", self._progress, "fraction")
|
||||
|
||||
self.bind_property("active", self._revealer, "reveal-child")
|
||||
self.bind_property("active", self._revealer, "vexpand")
|
||||
self.bind_property("reveal-widget", self._revealer, "child")
|
||||
self.bind_property("animation", self._revealer, "transition-type")
|
||||
|
||||
self._clicked.connect("released", self.__clicked)
|
||||
self.connect("notify::active", self.__notify_active)
|
||||
|
||||
self._box.append(self._icon)
|
||||
self._box.append(self._title)
|
||||
self._box.append(self._extra)
|
||||
self._box.append(self._arrow)
|
||||
|
||||
self._overlay.set_child(self._box)
|
||||
self._overlay.add_overlay(self._progress)
|
||||
self._overlay.add_controller(self._clicked)
|
||||
|
||||
self.append(self._overlay)
|
||||
self.append(self._revealer)
|
||||
|
||||
def __clicked(self, gesture: Gtk.GestureClick, n_press: int,
|
||||
x: int, y: int) -> None:
|
||||
self.active = True
|
||||
|
||||
def __notify_active(self, header, param) -> None:
|
||||
if self.active:
|
||||
self._arrow.set_state_flags(Gtk.StateFlags.CHECKED, False)
|
||||
else:
|
||||
self._arrow.unset_state_flags(Gtk.StateFlags.CHECKED)
|
|
@ -0,0 +1,167 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""Tests our sidebar Section Header widget."""
|
||||
import unittest
|
||||
import emmental.sidebar.header
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class TestHeader(unittest.TestCase):
|
||||
"""Tests the Section Header."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.header = emmental.sidebar.header.Header()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the Header is set up properly."""
|
||||
self.assertIsInstance(self.header, Gtk.Box)
|
||||
self.assertIsInstance(self.header._overlay, Gtk.Overlay)
|
||||
|
||||
self.assertEqual(self.header.get_orientation(),
|
||||
Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.assertIsInstance(self.header._box, Gtk.Box)
|
||||
self.assertEqual(self.header._box.get_spacing(), 12)
|
||||
self.assertEqual(self.header._box.get_orientation(),
|
||||
Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
self.assertEqual(self.header.get_first_child(), self.header._overlay)
|
||||
self.assertEqual(self.header._overlay.get_child(), self.header._box)
|
||||
|
||||
self.assertEqual(self.header._overlay.get_css_name(), "button")
|
||||
|
||||
def test_icon(self):
|
||||
"""Test the icon widget and icon-name property."""
|
||||
self.assertIsInstance(self.header._icon, Gtk.Image)
|
||||
self.assertEqual(self.header._box.get_first_child(), self.header._icon)
|
||||
|
||||
self.assertEqual(self.header.icon_name, "image-missing")
|
||||
self.assertEqual(self.header._icon.get_icon_name(), "image-missing")
|
||||
self.assertEqual(self.header._icon.get_icon_size(), Gtk.IconSize.LARGE)
|
||||
|
||||
self.header.icon_name = "audio-x-generic"
|
||||
self.assertEqual(self.header._icon.get_icon_name(), "audio-x-generic")
|
||||
|
||||
header2 = emmental.sidebar.header.Header(icon_name="audio-x-generic")
|
||||
self.assertEqual(header2._icon.get_icon_name(), "audio-x-generic")
|
||||
|
||||
def test_title(self):
|
||||
"""Test the title widget and property."""
|
||||
self.assertIsInstance(self.header._title, emmental.sidebar.title.Title)
|
||||
self.assertEqual(self.header._icon.get_next_sibling(),
|
||||
self.header._title)
|
||||
|
||||
self.assertEqual(self.header.title, "")
|
||||
self.assertEqual(self.header._title.title, "")
|
||||
|
||||
self.header.title = "Test Title"
|
||||
self.assertEqual(self.header._title.title, "Test Title")
|
||||
|
||||
header2 = emmental.sidebar.header.Header(title="Other Title")
|
||||
self.assertEqual(header2._title.title, "Other Title")
|
||||
|
||||
def test_subtitle(self):
|
||||
"""Test the subtitle property."""
|
||||
self.assertEqual(self.header.subtitle, "")
|
||||
self.assertEqual(self.header._title.subtitle, "")
|
||||
|
||||
self.header.subtitle = "Test Subtitle"
|
||||
self.assertEqual(self.header._title.subtitle, "Test Subtitle")
|
||||
|
||||
header2 = emmental.sidebar.header.Header(subtitle="Other Subtitle")
|
||||
self.assertEqual(header2._title.subtitle, "Other Subtitle")
|
||||
|
||||
def test_extra_widget(self):
|
||||
"""Test setting an extra widget."""
|
||||
self.assertIsInstance(self.header._extra, Adw.Bin)
|
||||
self.assertEqual(self.header._title.get_next_sibling(),
|
||||
self.header._extra)
|
||||
|
||||
self.assertIsNone(self.header.extra_widget)
|
||||
self.assertIsNone(self.header._extra.get_child())
|
||||
self.assertEqual(self.header._extra.get_valign(), Gtk.Align.CENTER)
|
||||
|
||||
self.header.extra_widget = Gtk.Button()
|
||||
self.assertEqual(self.header._extra.get_child(),
|
||||
self.header.extra_widget)
|
||||
|
||||
header2 = emmental.sidebar.header.Header(extra_widget=Gtk.Button())
|
||||
self.assertIsInstance(header2._extra.get_child(), Gtk.Button)
|
||||
|
||||
def test_active_arrow(self):
|
||||
"""Test the active property and arrow widget."""
|
||||
self.assertIsInstance(self.header._arrow, Gtk.Image)
|
||||
self.assertEqual(self.header._arrow.get_icon_name(),
|
||||
"go-down-symbolic")
|
||||
self.assertEqual(self.header._extra.get_next_sibling(),
|
||||
self.header._arrow)
|
||||
|
||||
self.assertFalse(self.header.active)
|
||||
flags = self.header._arrow.get_state_flags()
|
||||
self.assertFalse(flags & Gtk.StateFlags.CHECKED)
|
||||
|
||||
for active in [True, False]:
|
||||
with self.subTest(active=active):
|
||||
self.header.active = active
|
||||
flags = self.header._arrow.get_state_flags()
|
||||
self.assertEqual(bool(flags & Gtk.StateFlags.CHECKED), active)
|
||||
|
||||
def test_progress(self):
|
||||
"""Test the progress proprety and progress bar widget."""
|
||||
self.assertIsInstance(self.header._progress, Gtk.ProgressBar)
|
||||
self.assertEqual(self.header._progress.get_valign(), Gtk.Align.END)
|
||||
self.assertFalse(self.header._progress.get_visible())
|
||||
self.assertTrue(self.header._progress.has_css_class("osd"))
|
||||
self.assertIn(self.header._progress, self.header._overlay)
|
||||
|
||||
self.assertEqual(self.header.progress, 0.0)
|
||||
self.assertFalse(self.header.pending)
|
||||
|
||||
self.header.pending = True
|
||||
self.header.progress = 0.42
|
||||
self.assertTrue(self.header._progress.get_visible())
|
||||
self.assertAlmostEqual(self.header._progress.get_fraction(), 0.42)
|
||||
|
||||
header2 = emmental.sidebar.header.Header(pending=True)
|
||||
self.assertTrue(header2.pending)
|
||||
self.assertTrue(header2._progress.get_visible())
|
||||
|
||||
def test_revealer(self):
|
||||
"""Test the revealer."""
|
||||
self.assertIsInstance(self.header._revealer, Gtk.Revealer)
|
||||
self.assertEqual(self.header._overlay.get_next_sibling(),
|
||||
self.header._revealer)
|
||||
|
||||
self.assertEqual(self.header.animation,
|
||||
Gtk.RevealerTransitionType.SLIDE_UP)
|
||||
self.assertEqual(self.header._revealer.get_transition_type(),
|
||||
Gtk.RevealerTransitionType.SLIDE_UP)
|
||||
|
||||
self.assertIsNone(self.header.reveal_widget)
|
||||
self.assertIsNone(self.header._revealer.get_child())
|
||||
|
||||
self.header.reveal_widget = Gtk.Label()
|
||||
self.assertEqual(self.header._revealer.get_child(),
|
||||
self.header.reveal_widget)
|
||||
|
||||
self.header.active = True
|
||||
self.assertTrue(self.header._revealer.get_reveal_child())
|
||||
self.assertTrue(self.header._revealer.get_vexpand())
|
||||
|
||||
self.header.animation = Gtk.RevealerTransitionType.SLIDE_DOWN
|
||||
self.assertEqual(self.header._revealer.get_transition_type(),
|
||||
Gtk.RevealerTransitionType.SLIDE_DOWN)
|
||||
|
||||
def test_clicked(self):
|
||||
"""Test clicking the header."""
|
||||
self.assertIsInstance(self.header._clicked, Gtk.GestureClick)
|
||||
self.assertIn(self.header._clicked,
|
||||
self.header._overlay.observe_controllers())
|
||||
|
||||
for i in [1, 2]:
|
||||
with self.subTest(i=i):
|
||||
self.header._clicked.emit("released", 1, 42, 42)
|
||||
self.assertTrue(self.header.active)
|
||||
flags = self.header._arrow.get_state_flags()
|
||||
self.assertTrue(flags & Gtk.StateFlags.CHECKED)
|
Loading…
Reference in New Issue