diff --git a/p3/app/icons/credits.svg b/p3/app/icons/credits.svg new file mode 100644 index 00000000..92f26e76 --- /dev/null +++ b/p3/app/icons/credits.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/p3/app/icons/report.svg b/p3/app/icons/report.svg new file mode 100644 index 00000000..3308d41d --- /dev/null +++ b/p3/app/icons/report.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/p3/app/icons/sponsor.svg b/p3/app/icons/sponsor.svg new file mode 100644 index 00000000..d63e30cb --- /dev/null +++ b/p3/app/icons/sponsor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/p3/app/icons/wiki.svg b/p3/app/icons/wiki.svg new file mode 100644 index 00000000..78d6de2e --- /dev/null +++ b/p3/app/icons/wiki.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/p3/app/revealer.py b/p3/app/revealer.py new file mode 100644 index 00000000..56838797 --- /dev/null +++ b/p3/app/revealer.py @@ -0,0 +1,74 @@ +from .gtk_common import Gtk +from . import get_icon_path + + +class SupportFooter(Gtk.Box): + def __init__(self, translations): + super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=20) + self.set_margin_top(10) + self.translations = translations or {} + self.set_margin_bottom(10) + self.set_halign(Gtk.Align.CENTER) + + self.urls_labels = [ + ("https://linux.toys/knowledgebase.html", "Wiki", "wiki.svg"), + ("https://github.com/psygreg/linuxtoys/issues/new?template=bug_report.md", self.translations.get('report_label', 'Report Bug'), "report.svg"), + ("https://linux.toys/credits.html", self.translations.get('credits_label', 'Credits'), "credits.svg"), + ("https://ko-fi.com/psygreg", self.translations.get('support_footer', 'Support this project'), "sponsor.svg") + ] + + for i, (url, label, icon) in enumerate(self.urls_labels): + link_button = Gtk.LinkButton(uri=url, label=label) + if icon_path := get_icon_path(icon): + icon_img = Gtk.Image.new_from_file(icon_path) + link_button.set_image(icon_img) + self.pack_start(link_button, False, False, 0) + + if i < len(self.urls_labels) - 1: + separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) + self.pack_start(separator, False, False, 0) + + +class RevealerFooter(Gtk.Revealer): + def __init__(self, parent): + super().__init__() + + self.parent = parent + self.set_transition_type(Gtk.RevealerTransitionType.SLIDE_UP) + self.set_transition_duration(300) + + container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + + self.button_box = Gtk.ButtonBox(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + self.button_box.set_layout(Gtk.ButtonBoxStyle.CENTER) + self.button_box.set_margin_top(5) + self.button_box.set_margin_bottom(5) + + self.button_next = Gtk.Button(label=self.parent.translations.get("next", "Next")) + self.button_next.set_image(Gtk.Image.new_from_icon_name("go-next", Gtk.IconSize.BUTTON)) + self.button_next.set_always_show_image(True) + self.button_next.set_tooltip_text(self.parent.translations.get("next", "Next")) + self.button_next.set_size_request(125, 35) + self.button_next.connect("clicked", self._on_next_clicked) + + self.button_cancel = Gtk.Button(label=self.parent.translations.get("cancel", "Cancel")) + self.button_cancel.set_image(Gtk.Image.new_from_icon_name("window-close", Gtk.IconSize.BUTTON)) + self.button_cancel.set_always_show_image(True) + self.button_cancel.set_tooltip_text(self.parent.translations.get("cancel", "Cancel")) + self.button_cancel.set_size_request(125, 35) + self.button_cancel.connect("clicked", self._on_cancel_clicked) + + self.button_box.add(self.button_cancel) + self.button_box.add(self.button_next) + + self.support = SupportFooter(self.parent.translations) + + container.pack_start(self.support, False, False, 0) + container.pack_start(self.button_box, False, False, 0) + self.add(container) + + def _on_next_clicked(self, button): + self.parent.on_install_checklist(button) + + def _on_cancel_clicked(self, button): + self.parent.on_cancel_checklist(button) \ No newline at end of file diff --git a/p3/app/style.css b/p3/app/style.css index 44cd8a8e..5eaf4341 100644 --- a/p3/app/style.css +++ b/p3/app/style.css @@ -1,9 +1,10 @@ -/* Defines the style for each item in our grid (FlowBox) */ +* { + outline: none; +} .script-item { background-color: rgba(0,0,0,0.1); - border: 1px solid rgba(255, 255, 255, 0.3); /* borda branca semi-transparente */ + border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 16px; - /* Let GTK use system theme colors automatically */ } .script-item-hover { background-color: rgba(255, 255, 255, 0.05); @@ -21,7 +22,4 @@ } flowboxchild:selected { border-radius: 16px; -} -flowboxchild { - outline: none; } \ No newline at end of file diff --git a/p3/app/window.py b/p3/app/window.py index 9d69a42d..fbdbbcea 100644 --- a/p3/app/window.py +++ b/p3/app/window.py @@ -4,13 +4,13 @@ from . import parser from . import header -from . import footer from . import compat from . import head_menu from . import reboot_helper from . import search_helper from . import get_icon_path from . import term_view +from . import revealer class AppWindow(Gtk.ApplicationWindow): def __init__(self, application, translations, *args, **kwargs): @@ -100,8 +100,8 @@ def __init__(self, application, translations, *args, **kwargs): self.search_view.add(self.search_flowbox) self.main_stack.add_named(self.search_view, "search") - self.footer_widget = footer.create_footer() - main_vbox.pack_start(self.footer_widget, False, False, 0) + self.reveal = revealer.RevealerFooter(self) + main_vbox.pack_start(self.reveal, False, False, 0) # --- Load Data and Connect Signals --- self.load_categories() @@ -342,6 +342,18 @@ def create_flowbox(self): flowbox.set_row_spacing(12) ## espaço entre linhas return flowbox + def _on_toggled_check(self, button): + if button.get_active(): + if button not in self.check_buttons: + self.check_buttons.append(button) + else: + if button in self.check_buttons: + self.check_buttons.remove(button) + + self.reveal.button_box.show_all() + self.reveal.support.hide() + self.reveal.set_reveal_child(len(self.check_buttons) >= 2) + def create_item_widget(self, item_info, checklist: bool = False): import os box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) @@ -370,9 +382,9 @@ def create_item_widget(self, item_info, checklist: bool = False): if checklist: check = Gtk.CheckButton() + check.connect('toggled', self._on_toggled_check) check.script_info = item_info box.pack_start(check, False, False, 0) - self.check_buttons.append(check) if is_subcategory or (is_category_type and is_not_script) or (is_main_category and is_not_script): # This is a category or subcategory - make it bold @@ -512,17 +524,7 @@ def _load_scripts_into_flowbox(self, flowbox, category_info): flowbox.add(widget) if checklist_mode: - # Clear previous checklist buttons from footer - for child in self.footer_widget.checklist_button_box.get_children(): - self.footer_widget.checklist_button_box.remove(child) - - install_btn = Gtk.Button(label=self.translations.get('install_btn_label', 'Install')) - cancel_btn = Gtk.Button(label=self.translations.get('cancel_btn_label', 'Cancel')) - install_btn.connect("clicked", self.on_install_checklist) - cancel_btn.connect("clicked", self.on_cancel_checklist) - self.footer_widget.checklist_button_box.pack_start(install_btn, False, False, 0) - self.footer_widget.checklist_button_box.pack_start(cancel_btn, False, False, 0) - self.footer_widget.checklist_button_box.show_all() + self.reveal.set_reveal_child(len(self.check_buttons) >= 2) def load_scripts(self, category_info): """Loads scripts for a category and connects their click event. Supports checklist mode.""" @@ -540,18 +542,14 @@ def on_install_checklist(self, button): if not selected_scripts: return - self.open_term_view(selected_scripts) + for cb in self.check_buttons[:]: cb.set_active(False) - for cb in self.check_buttons: cb.set_active(False) + self.open_term_view(selected_scripts) def on_cancel_checklist(self, button): """Uncheck all boxes, remove checklist buttons from footer, and return to previous view.""" - for cb in self.check_buttons: - cb.set_active(False) - # Remove checklist buttons from footer - for child in self.footer_widget.checklist_button_box.get_children(): - self.footer_widget.checklist_button_box.remove(child) - + for cb in self.check_buttons[:]: cb.set_active(False) + # Use back button logic to go to the appropriate previous view self.on_back_button_clicked(None) @@ -605,9 +603,9 @@ def open_term_view(self, infos): run_box = term_view.TermRunScripts(infos, self, self.translations) self.header_widget.hide() - + self.reveal.set_reveal_child(False) + self.check_buttons.clear() self.back_button.show() - self.footer_widget.hide() child = self.main_stack.get_child_by_name("running_scripts") if child is not None: @@ -742,10 +740,6 @@ def _refresh_ui_with_new_translations(self): if hasattr(self, 'menu_button'): self.menu_button.refresh_menu_translations() - # Refresh the footer with new translations - if hasattr(self.footer_widget, 'refresh_translations'): - self.footer_widget.refresh_translations(self.translations) - # Always reload categories with new translations (so they're ready when user navigates back) self.load_categories() @@ -767,24 +761,14 @@ def _refresh_ui_with_new_translations(self): # Update title bar with fresh category name category_name = self.current_category_info.get('name', 'Unknown') self.header_bar.props.title = f"LinuxToys: {category_name}" - + # Reload the scripts view with new translations self.load_scripts(self.current_category_info) - + # Update footer if in checklist mode if (self.current_category_info and self.current_category_info.get('display_mode', 'menu') == 'checklist'): - # Clear and recreate checklist buttons with new translations - for child in self.footer_widget.checklist_button_box.get_children(): - self.footer_widget.checklist_button_box.remove(child) - - install_btn = Gtk.Button(label=self.translations.get('install_btn_label', 'Install')) - cancel_btn = Gtk.Button(label=self.translations.get('cancel_btn_label', 'Cancel')) - install_btn.connect("clicked", self.on_install_checklist) - cancel_btn.connect("clicked", self.on_cancel_checklist) - self.footer_widget.checklist_button_box.pack_start(install_btn, False, False, 0) - self.footer_widget.checklist_button_box.pack_start(cancel_btn, False, False, 0) - self.footer_widget.checklist_button_box.show_all() + self.reveal.set_reveal_child(len(self.check_buttons) >= 2) def _get_fresh_category_info_with_translations(self): """Get fresh category info with updated translations""" @@ -1413,6 +1397,8 @@ def _display_search_results(self): # Ensure the back button is visible when in search mode self.back_button.show() + self.reveal.set_reveal_child(False) + # Disable drag-and-drop in search mode self._disable_drag_and_drop() @@ -1461,6 +1447,9 @@ def _clear_search_results(self): self.main_stack.set_visible_child(self.scripts_view) # Ensure back button is visible for category views self.back_button.show() + + if self.current_category_info.get('display_mode', 'menu') == 'checklist': + self.reveal.set_reveal_child(len(self.check_buttons) >= 2) # Restore drag-and-drop state based on current category if self._is_local_scripts_category(self.current_category_info): @@ -1498,6 +1487,8 @@ def on_back_button_clicked(self, widget): self.search_entry.set_text("") self._clear_search_results() return + + self.check_buttons.clear() if self.navigation_stack: # Store current view for cleanup @@ -1545,11 +1536,9 @@ def on_back_button_clicked(self, widget): # Show footer only if checklist mode if previous_category.get('display_mode', 'menu') == 'checklist': - self.footer_widget.show() - self.footer_widget.show_checklist_footer() - self.footer_widget.set_margin_bottom(0) + self.reveal.set_reveal_child(len(self.check_buttons) >= 2) else: - self.footer_widget.hide() + self.reveal.set_reveal_child(False) # Clean up the old view after transition def cleanup_old_view(): @@ -1589,11 +1578,9 @@ def show_categories_view(self): self.back_button.hide() self.header_bar.props.title = "LinuxToys" self._update_header() # Reset to default header - self.header_widget.show() - self.footer_widget.show() - self.footer_widget.show_menu_footer() - # Ensure footer has proper spacing - self.footer_widget.set_margin_bottom(0) + self.reveal.set_reveal_child(True) + self.reveal.button_box.hide() + self.reveal.support.show_all() # Disable drag-and-drop when viewing main categories self._disable_drag_and_drop() @@ -1629,9 +1616,6 @@ def show_scripts_view(self, category_info): # Show footer only if checklist mode if category_info and category_info.get('display_mode', 'menu') == 'checklist': - self.footer_widget.show() - self.footer_widget.show_checklist_footer() - # Ensure footer has proper spacing for checklist mode - self.footer_widget.set_margin_bottom(0) + self.reveal.set_reveal_child(len(self.check_buttons) >= 2) else: - self.footer_widget.hide() + self.reveal.set_reveal_child(False) \ No newline at end of file