From e5d2f01f278daa08035693d7f322c357a646e371 Mon Sep 17 00:00:00 2001 From: Zsolt Kovari <7029304+zkovari@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:21:37 +0200 Subject: [PATCH] Add Plotlyst roadmap panel (#1337) * Add Plotlyst roadmap panel * progress * fix tests * version and link * tags * filter by version * task summary tooltip * header * counter * version counters * filter by tags * space * buttons --- src/main/python/plotlyst/core/domain.py | 6 +- src/main/python/plotlyst/view/home_view.py | 7 +- src/main/python/plotlyst/view/main_window.py | 2 + src/main/python/plotlyst/view/roadmap_view.py | 338 +++++++++++++ src/main/python/plotlyst/view/widget/task.py | 72 ++- ui/home_view.ui | 23 +- ui/roadmap_view.ui | 475 ++++++++++++++++++ 7 files changed, 886 insertions(+), 37 deletions(-) create mode 100644 src/main/python/plotlyst/view/roadmap_view.py create mode 100644 ui/roadmap_view.ui diff --git a/src/main/python/plotlyst/core/domain.py b/src/main/python/plotlyst/core/domain.py index 2eb0ec347..3b01775b9 100644 --- a/src/main/python/plotlyst/core/domain.py +++ b/src/main/python/plotlyst/core/domain.py @@ -2231,7 +2231,7 @@ class SensoryPerception: class SensoryDetail: night_mode: bool = field(default=False, metadata=config(exclude=exclude_if_false)) perceptions: Dict[str, SensoryPerception] = field(default_factory=dict, - metadata=config(exclude=exclude_if_empty)) + metadata=config(exclude=exclude_if_empty)) @dataclass @@ -2319,6 +2319,9 @@ class Task(CharacterBased): summary: str = field(default='', metadata=config(exclude=exclude_if_empty)) character_id: Optional[uuid.UUID] = None tags: List[str] = field(default_factory=list, metadata=config(exclude=exclude_if_empty)) + web_link: str = field(default='', metadata=config(exclude=exclude_if_empty)) + version: str = field(default='', metadata=config(exclude=exclude_if_empty)) + beta: bool = field(default=False, metadata=config(exclude=exclude_if_false)) def __post_init__(self): if self.creation_date is None: @@ -2355,6 +2358,7 @@ def default_task_statues() -> List[TaskStatus]: class Board: tasks: List[Task] = field(default_factory=list) statuses: List[TaskStatus] = field(default_factory=default_task_statues) + tags: Dict[str, SelectionItem] = field(default_factory=dict, metadata=config(exclude=exclude_if_empty)) class TemplateStoryStructureType(Enum): diff --git a/src/main/python/plotlyst/view/home_view.py b/src/main/python/plotlyst/view/home_view.py index 9cfcb04f5..328d77dde 100644 --- a/src/main/python/plotlyst/view/home_view.py +++ b/src/main/python/plotlyst/view/home_view.py @@ -43,6 +43,7 @@ from plotlyst.view.dialog.home import StoryCreationDialog from plotlyst.view.generated.home_view_ui import Ui_HomeView from plotlyst.view.icons import IconRegistry +from plotlyst.view.roadmap_view import RoadmapView from plotlyst.view.style.base import apply_border_image from plotlyst.view.style.button import apply_button_palette_color from plotlyst.view.widget.confirm import confirmed @@ -112,7 +113,6 @@ def __init__(self): self.ui.btnTutorials.setHidden(True) self.ui.btnProgress.setHidden(True) - self.ui.btnRoadmap.setHidden(True) self.ui.lblWelcomeMain.setText(home_page_welcome_text) @@ -181,6 +181,9 @@ def __init__(self): (self.ui.btnProgress, self.ui.pageProgress), (self.ui.btnRoadmap, self.ui.pageRoadmap)]) + self._roadmapView = RoadmapView() + self.ui.pageRoadmap.layout().addWidget(self._roadmapView) + # self._tutorialsTreeView = TutorialsTreeView(settings=TreeSettings(font_incr=2)) # self._tutorialsTreeView.tutorialSelected.connect(self._tutorial_selected) # self.ui.splitterTutorials.setSizes([150, 500]) @@ -201,7 +204,7 @@ def __init__(self): # incr_font(self.ui.lineTutorialTitle, 10) # bold(self.ui.lineTutorialTitle) - self.ui.btnLibrary.setChecked(True) + self.ui.btnRoadmap.setChecked(True) self.ui.stackWdgNovels.setCurrentWidget(self.ui.pageEmpty) self._novels = client.novels() diff --git a/src/main/python/plotlyst/view/main_window.py b/src/main/python/plotlyst/view/main_window.py index a359f5acd..513bd56c4 100644 --- a/src/main/python/plotlyst/view/main_window.py +++ b/src/main/python/plotlyst/view/main_window.py @@ -189,6 +189,8 @@ def __init__(self, *args, **kwargs): QApplication.instance().installEventFilter(CapitalizationEventFilter(self)) + self.home_mode.setChecked(True) + @overrides def closeEvent(self, event: QCloseEvent) -> None: if language_tool_proxy.is_set(): diff --git a/src/main/python/plotlyst/view/roadmap_view.py b/src/main/python/plotlyst/view/roadmap_view.py new file mode 100644 index 000000000..554624491 --- /dev/null +++ b/src/main/python/plotlyst/view/roadmap_view.py @@ -0,0 +1,338 @@ +""" +Plotlyst +Copyright (C) 2021-2024 Zsolt Kovari + +This file is part of Plotlyst. + +Plotlyst is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plotlyst is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +import datetime +from functools import partial +from typing import Optional, Dict, Set + +from PyQt6.QtCore import QEvent, QThreadPool, QSize, Qt, pyqtSignal +from PyQt6.QtGui import QFont +from PyQt6.QtWidgets import QWidget, QSizePolicy, QFrame +from overrides import overrides +from qthandy import clear_layout, hbox, spacer, margins, vbox, incr_font, retain_when_hidden, decr_icon, translucent, \ + decr_font, transparent, vspacer, italic +from qthandy.filter import VisibilityToggleEventFilter, OpacityEventFilter + +from plotlyst.common import PLOTLYST_SECONDARY_COLOR, PLOTLYST_MAIN_COLOR, RELAXED_WHITE_COLOR +from plotlyst.core.domain import Board, Task, TaskStatus +from plotlyst.core.template import SelectionItem +from plotlyst.env import app_env +from plotlyst.service.resource import JsonDownloadWorker, JsonDownloadResult +from plotlyst.view.common import push_btn, spin, shadow, tool_btn, open_url, ButtonPressResizeEventFilter +from plotlyst.view.generated.roadmap_view_ui import Ui_RoadmapView +from plotlyst.view.icons import IconRegistry +from plotlyst.view.layout import group +from plotlyst.view.style.button import apply_button_palette_color +from plotlyst.view.widget.input import AutoAdjustableTextEdit +from plotlyst.view.widget.task import BaseStatusColumnWidget +from plotlyst.view.widget.tree import TreeView, EyeToggleNode + +tags_counter: Dict[str, int] = {} +versions_counter: Dict[str, int] = {} + + +class TagsTreeView(TreeView): + toggled = pyqtSignal(str, bool) + + def __init__(self, parent=None): + super().__init__(parent) + + def setTags(self, tags: Dict[str, SelectionItem]): + self.clear() + for k, v in tags.items(): + node = EyeToggleNode(f'{k.capitalize()} ({tags_counter.get(k, 0)})', + IconRegistry.from_name(v.icon, color=v.icon_color)) + node.setToolTip(v.text) + node.toggled.connect(partial(self.toggled.emit, k)) + self._centralWidget.layout().addWidget(node) + + self._centralWidget.layout().addWidget(vspacer()) + + def clear(self): + clear_layout(self._centralWidget) + + +class RoadmapTaskWidget(QFrame): + + def __init__(self, task: Task, tags: Dict[str, SelectionItem], parent=None): + super().__init__(parent) + self._task: Task = task + + self.setProperty('relaxed-white-bg', True) + self.setProperty('rounded', True) + + vbox(self, margin=5) + self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum) + self.setMinimumHeight(75) + shadow(self, 3) + + self._textTitle = AutoAdjustableTextEdit(self) + self._textTitle.setPlaceholderText('New task') + self._textTitle.setText(task.title) + self.setToolTip(task.summary) + transparent(self._textTitle) + self._textTitle.setReadOnly(True) + font = QFont(app_env.sans_serif_font()) + font.setWeight(QFont.Weight.Medium) + self._textTitle.setFont(font) + incr_font(self._textTitle) + + self._btnOpenInExternal = tool_btn(IconRegistry.from_name('fa5s.external-link-alt', 'grey'), transparent_=True, + tooltip='Open in browser') + self._btnOpenInExternal.clicked.connect(lambda: open_url(self._task.web_link)) + retain_when_hidden(self._btnOpenInExternal) + decr_icon(self._btnOpenInExternal, 4) + + top_wdg = group(self._textTitle, spacer(), self._btnOpenInExternal, margin=0, spacing=1) + self.layout().addWidget(top_wdg, alignment=Qt.AlignmentFlag.AlignTop) + + self._wdgBottom = QWidget() + retain_when_hidden(self._wdgBottom) + hbox(self._wdgBottom) + + for tag_name in self._task.tags: + tag = tags.get(tag_name) + if tag: + btn = tool_btn(IconRegistry.from_name(tag.icon, tag.icon_color), transparent_=True, icon_resize=False, + pointy_=False, tooltip=tag.text) + decr_icon(btn, 4) + translucent(btn, 0.7) + self._wdgBottom.layout().addWidget(btn) + + self._wdgBottom.layout().addWidget(spacer()) + if self._task.version == 'Plus': + self._btnVersion = push_btn(text=self._task.version, properties=['transparent'], + tooltip='Feature will be available in Plotlyst Plus', icon_resize=False, + pointy_=False) + self._btnVersion.setIcon(IconRegistry.from_name('mdi.certificate', color=PLOTLYST_MAIN_COLOR)) + apply_button_palette_color(self._btnVersion, PLOTLYST_MAIN_COLOR) + decr_font(self._btnVersion) + decr_icon(self._btnVersion, 2) + self._wdgBottom.layout().addWidget(self._btnVersion) + + self.layout().addWidget(self._wdgBottom, alignment=Qt.AlignmentFlag.AlignBottom) + + self.installEventFilter(VisibilityToggleEventFilter(self._btnOpenInExternal, self)) + + def task(self) -> Task: + return self._task + + +class RoadmapStatusColumn(BaseStatusColumnWidget): + + def __init__(self, status: TaskStatus, parent=None): + super().__init__(status, parent, collapseEnabled=False, headerAdditionEnabled=False) + + def addTask(self, task: Task, board: Board) -> RoadmapTaskWidget: + wdg = RoadmapTaskWidget(task, board.tags, self) + self._container.layout().insertWidget(self._container.layout().count() - 1, wdg, + alignment=Qt.AlignmentFlag.AlignTop) + + self._header.updateTitle(self._container.layout().count() - 1) + return wdg + + +class RoadmapBoardWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + hbox(self, spacing=20) + self._statusColumns: Dict[str, RoadmapStatusColumn] = {} + self._tasks: Dict[Task, RoadmapTaskWidget] = {} + self._tagFilters: Set[str] = set() + + def setBoard(self, board: Board): + clear_layout(self) + self._statusColumns.clear() + self._tasks.clear() + self._tagFilters.clear() + self._version: str = '' + self._beta: bool = False + + for status in board.statuses: + column = RoadmapStatusColumn(status) + self.layout().addWidget(column) + self._statusColumns[str(status.id)] = column + + for task in board.tasks: + column = self._statusColumns.get(str(task.status_ref)) + wdg = column.addTask(task, board) + self._tasks[task] = wdg + for tag in task.tags: + if tag not in tags_counter: + tags_counter[tag] = 0 + tags_counter[tag] += 1 + if task.beta: + versions_counter['Beta'] += 1 + if task.version: + versions_counter[task.version] += 1 + + _spacer = spacer() + _spacer.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + self.layout().addWidget(_spacer) + margins(self, left=20) + + def showAll(self): + self._version = '' + self._beta = False + + for task, wdg in self._tasks.items(): + wdg.setVisible(self._filter(task)) + + def filterVersion(self, version: str): + self._version = version + self._beta = False + + for task, wdg in self._tasks.items(): + wdg.setVisible(self._filter(task)) + + def filterBeta(self): + self._version = '' + self._beta = True + + for task, wdg in self._tasks.items(): + wdg.setVisible(self._filter(task)) + + def filterTag(self, tag: str, filtered: bool): + if filtered: + self._tagFilters.add(tag) + else: + self._tagFilters.remove(tag) + + for task, wdg in self._tasks.items(): + wdg.setVisible(self._filter(task)) + + def _filter(self, task: Task) -> bool: + if self._version and task.version != self._version: + return False + if self._beta and not task.beta: + return False + + return self._filteredByTags(task) + + def _filteredByTags(self, task: Task) -> bool: + if not self._tagFilters: + return True + + for tag in task.tags: + if tag in self._tagFilters: + return True + return False + + +class RoadmapView(QWidget, Ui_RoadmapView): + + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(self) + + self.btnRoadmapIcon.setIcon(IconRegistry.from_name('fa5s.road')) + self.btnPlus.setIcon(IconRegistry.from_name('mdi.certificate', color_on=PLOTLYST_SECONDARY_COLOR)) + self.btnVisitRoadmap.setIcon(IconRegistry.from_name('fa5s.external-link-alt')) + self.btnVisitRoadmap.installEventFilter(ButtonPressResizeEventFilter(self.btnVisitRoadmap)) + self.btnVisitRoadmap.clicked.connect(lambda: open_url('https://plotlyst.featurebase.app/roadmap')) + decr_icon(self.btnVisitRoadmap, 2) + decr_font(self.btnVisitRoadmap) + italic(self.btnVisitRoadmap) + self.btnVisitRoadmap.installEventFilter(OpacityEventFilter(self.btnVisitRoadmap, enterOpacity=0.7)) + + self.btnSubmitRequest.setIcon(IconRegistry.from_name('mdi.comment-text', RELAXED_WHITE_COLOR)) + self.btnSubmitRequest.installEventFilter(ButtonPressResizeEventFilter(self.btnSubmitRequest)) + self.btnSubmitRequest.clicked.connect(lambda: open_url('https://plotlyst.featurebase.app/')) + + self.splitter.setSizes([150, 550]) + + self._last_fetched = None + self._downloading = False + self._board: Optional[Board] = None + self._thread_pool = QThreadPool() + + self._roadmapWidget = RoadmapBoardWidget() + self.scrollAreaWidgetContents.layout().addWidget(self._roadmapWidget) + + self._tagsTree = TagsTreeView() + self.wdgCategoriesParent.layout().addWidget(self._tagsTree) + self._tagsTree.toggled.connect(self._roadmapWidget.filterTag) + + self.btnAll.clicked.connect(self._roadmapWidget.showAll) + self.btnFree.clicked.connect(lambda: self._roadmapWidget.filterVersion('Free')) + self.btnPlus.clicked.connect(lambda: self._roadmapWidget.filterVersion('Plus')) + self.btnBeta.clicked.connect(self._roadmapWidget.filterBeta) + + self.wdgLoading.setHidden(True) + + @overrides + def showEvent(self, event: QEvent): + super().showEvent(event) + + if self._downloading: + return + + if self._last_fetched is None or (datetime.datetime.now() - self._last_fetched).total_seconds() > 86400: + self._handle_downloading_status(True) + self._download_data() + + def _download_data(self): + result = JsonDownloadResult() + runnable = JsonDownloadWorker("https://raw.githubusercontent.com/plotlyst/feed/refs/heads/main/posts.json", + result) + result.finished.connect(self._handle_downloaded_data) + result.failed.connect(self._handle_download_failure) + self._thread_pool.start(runnable) + + def _handle_downloaded_data(self, data): + self.btnAll.setChecked(True) + tags_counter.clear() + versions_counter.clear() + versions_counter['Free'] = 0 + versions_counter['Plus'] = 0 + versions_counter['Beta'] = 0 + + self._board = Board.from_dict(data) + self._roadmapWidget.setBoard(self._board) + self._tagsTree.setTags(self._board.tags) + + self.btnFree.setText(f'Free ({versions_counter.get("Free", 0)})') + self.btnPlus.setText(f'Plus ({versions_counter.get("Plus", 0)})') + self.btnBeta.setText(f'Beta ({versions_counter.get("Beta", 0)})') + + now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") + self.lblLastUpdated.setText(f"Last updated: {now}") + self._last_fetched = datetime.datetime.now() + + self._handle_downloading_status(False) + + def _handle_download_failure(self, status_code: int, message: str): + if self._board is None: + self.lblLastUpdated.setText("Failed to update data.") + self._handle_downloading_status(False) + + def _handle_downloading_status(self, loading: bool): + self._downloading = loading + self.scrollAreaWidgetContents.setDisabled(loading) + self.splitter.setHidden(loading) + self.wdgTopSelectors.setHidden(loading) + self.wdgLoading.setVisible(loading) + if loading: + btn = push_btn(transparent_=True) + btn.setIconSize(QSize(128, 128)) + self.wdgLoading.layout().addWidget(btn, + alignment=Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter) + spin(btn, PLOTLYST_SECONDARY_COLOR) + else: + clear_layout(self.wdgLoading) diff --git a/src/main/python/plotlyst/view/widget/task.py b/src/main/python/plotlyst/view/widget/task.py index 71de25697..7d5c88dad 100644 --- a/src/main/python/plotlyst/view/widget/task.py +++ b/src/main/python/plotlyst/view/widget/task.py @@ -40,7 +40,7 @@ TaskChangedFromWip from plotlyst.service.persistence import RepositoryPersistenceManager from plotlyst.view.common import ButtonPressResizeEventFilter, shadow, action, tool_btn, \ - any_menu_visible + any_menu_visible, insert_before_the_end from plotlyst.view.icons import IconRegistry from plotlyst.view.layout import group from plotlyst.view.widget.button import CollapseButton, TaskTagSelector @@ -186,7 +186,8 @@ class _StatusHeader(QFrame): collapseToggled = pyqtSignal(bool) addTask = pyqtSignal() - def __init__(self, status: TaskStatus, parent=None): + def __init__(self, status: TaskStatus, parent=None, collapseEnabled: bool = True, + headerAdditionEnabled: bool = True): super(_StatusHeader, self).__init__(parent) self._status = status self.setStyleSheet(f'''_StatusHeader {{ @@ -199,7 +200,10 @@ def __init__(self, status: TaskStatus, parent=None): bold(self._title) self.updateTitle() self._btnCollapse = CollapseButton(Qt.Edge.BottomEdge, Qt.Edge.LeftEdge, self) - self.installEventFilter(VisibilityToggleEventFilter(self._btnCollapse, self)) + if collapseEnabled: + self.installEventFilter(VisibilityToggleEventFilter(self._btnCollapse, self)) + else: + self._btnCollapse.setHidden(True) shadow(self) self._btnAdd = QToolButton() @@ -219,7 +223,10 @@ def __init__(self, status: TaskStatus, parent=None): ''') pointy(self._btnAdd) self._btnAdd.installEventFilter(ButtonPressResizeEventFilter(self._btnAdd)) - self.installEventFilter(VisibilityToggleEventFilter(self._btnAdd, self)) + if headerAdditionEnabled: + self.installEventFilter(VisibilityToggleEventFilter(self._btnAdd, self)) + else: + self._btnAdd.setHidden(True) self.layout().addWidget(self._title) self.layout().addWidget(self._btnCollapse) @@ -237,25 +244,42 @@ def updateTitle(self, childrenCount: int = 0): self._title.setText(f'{self._status.text.upper()} {suffix}') -class StatusColumnWidget(QFrame): - taskChanged = pyqtSignal(Task) - taskDeleted = pyqtSignal(Task) - taskResolved = pyqtSignal(Task) - - def __init__(self, novel: Novel, status: TaskStatus, parent=None): - super(StatusColumnWidget, self).__init__(parent) - self._novel = novel +class BaseStatusColumnWidget(QFrame): + def __init__(self, status: TaskStatus, parent=None, collapseEnabled: bool = True, + headerAdditionEnabled: bool = True): + super().__init__(parent) self._status = status + vbox(self, 1, 20) - self._header = _StatusHeader(self._status) + self._header = _StatusHeader(self._status, collapseEnabled=collapseEnabled, + headerAdditionEnabled=headerAdditionEnabled) self._header.collapseToggled.connect(self._collapseToggled) - self._container = QWidget(self) + self._container = QFrame(self) + self._container.setProperty('darker-bg', True) + self._container.setProperty('rounded', True) spacing = 6 if app_env.is_mac() else 12 - vbox(self._container, margin=5, spacing=spacing) + vbox(self._container, margin=10, spacing=spacing) + self._container.layout().addWidget(vspacer()) + self.setMaximumWidth(TASK_WIDGET_MAX_WIDTH) self.layout().addWidget(self._header) self.layout().addWidget(self._container) - self.layout().addWidget(vspacer()) + + def _collapseToggled(self, toggled: bool): + for i in range(self._container.layout().count()): + item = self._container.layout().itemAt(i) + if item.widget() and isinstance(item.widget(), TaskWidget): + item.widget().setHidden(toggled) + + +class StatusColumnWidget(BaseStatusColumnWidget): + taskChanged = pyqtSignal(Task) + taskDeleted = pyqtSignal(Task) + taskResolved = pyqtSignal(Task) + + def __init__(self, novel: Novel, status: TaskStatus, parent=None): + super(StatusColumnWidget, self).__init__(status, parent) + self._novel = novel self._btnAdd = QPushButton('New Task', self) self._btnAdd.setIcon(IconRegistry.plus_icon('grey')) @@ -265,7 +289,7 @@ def __init__(self, novel: Novel, status: TaskStatus, parent=None): self._btnAdd.installEventFilter(ButtonPressResizeEventFilter(self._btnAdd)) self._btnAdd.installEventFilter(OpacityEventFilter(self._btnAdd)) - self._container.layout().addWidget(self._btnAdd, alignment=Qt.AlignmentFlag.AlignLeft) + insert_before_the_end(self._container, self._btnAdd, alignment=Qt.AlignmentFlag.AlignLeft) self.installEventFilter(VisibilityToggleEventFilter(self._btnAdd, self)) self.setAcceptDrops(True) @@ -281,7 +305,7 @@ def __init__(self, novel: Novel, status: TaskStatus, parent=None): def event_received(self, event: Event): if isinstance(event, CharacterDeletedEvent): - for i in range(self._container.layout().count() - 1): + for i in range(self._container.layout().count() - 2): item = self._container.layout().itemAt(i) if item.widget(): taskWdg: TaskWidget = item.widget() @@ -294,7 +318,7 @@ def status(self) -> TaskStatus: def addTask(self, task: Task, edit: bool = False) -> TaskWidget: wdg = TaskWidget(task, self) - self._container.layout().insertWidget(self._container.layout().count() - 1, wdg, + self._container.layout().insertWidget(self._container.layout().count() - 2, wdg, alignment=Qt.AlignmentFlag.AlignTop) wdg.installEventFilter( DragEventFilter(self, mimeType=TASK_MIME_TYPE, dataFunc=self._grabbedTaskData, @@ -310,7 +334,7 @@ def addTask(self, task: Task, edit: bool = False) -> TaskWidget: if self._status.wip: emit_event(self._novel, TaskChangedToWip(self, task)) - self._header.updateTitle(self._container.layout().count() - 1) + self._header.updateTitle(self._container.layout().count() - 2) return wdg def _addNewTask(self): @@ -324,12 +348,6 @@ def _deleteTask(self, taskWidget: TaskWidget): self.__removeTaskWidget(taskWidget) self.taskDeleted.emit(task) - def _collapseToggled(self, toggled: bool): - for i in range(self._container.layout().count()): - item = self._container.layout().itemAt(i) - if item.widget() and isinstance(item.widget(), TaskWidget): - item.widget().setHidden(toggled) - def _grabbedTaskData(self, widget: TaskWidget): return widget.task() @@ -365,7 +383,7 @@ def __removeTaskWidget(self, taskWidget): taskWidget.setHidden(True) self._container.layout().removeWidget(taskWidget) gc(taskWidget) - self._header.updateTitle(self._container.layout().count() - 1) + self._header.updateTitle(self._container.layout().count() - 2) class BoardWidget(QWidget): diff --git a/ui/home_view.ui b/ui/home_view.ui index df640e987..b5d568537 100644 --- a/ui/home_view.ui +++ b/ui/home_view.ui @@ -254,6 +254,22 @@ + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + @@ -1284,13 +1300,6 @@ It offers a range of tools to support various stages of novel writing, including 0 - - - - Coming soon - - - diff --git a/ui/roadmap_view.ui b/ui/roadmap_view.ui new file mode 100644 index 000000000..4a420cf0b --- /dev/null +++ b/ui/roadmap_view.ui @@ -0,0 +1,475 @@ + + + RoadmapView + + + + 0 + 0 + 554 + 453 + + + + Form + + + + 3 + + + 15 + + + 5 + + + 15 + + + 5 + + + + + + 0 + + + 0 + + + + + + + + true + + + + + + + + 0 + 0 + + + + Plotlyst roadmap + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + true + + + + + + + PointingHandCursor + + + Visit online board + + + true + + + + + + + + + + + 0 + + + 15 + + + 0 + + + 5 + + + 0 + + + + + Planned, in-progress, and completed tasks for both the Free and Plotlyst Plus versions. Beta: upcoming features that are currently available in the Beta version. + + + true + + + true + + + + + + + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 70 + 0 + + + + PointingHandCursor + + + Display all tasks + + + All + + + true + + + true + + + true + + + true + + + buttonGroup + + + + + + + + 70 + 0 + + + + PointingHandCursor + + + Display tasks that belong to the free verison of Plotlyst + + + Free + + + true + + + false + + + true + + + true + + + buttonGroup + + + + + + + + 70 + 0 + + + + PointingHandCursor + + + Display tasks that belong to Plotlyst Plus + + + Plus + + + true + + + false + + + true + + + true + + + buttonGroup + + + + + + + Qt::Vertical + + + + + + + + 70 + 0 + + + + PointingHandCursor + + + Display done tasks that are currently available in the Beta version + + + Beta + + + true + + + false + + + true + + + true + + + buttonGroup + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + PointingHandCursor + + + Submit a request + + + true + + + true + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + 10 + + + false + + + true + + + + + 0 + 0 + + + + + 3 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 343 + 176 + + + + + 0 + 0 + + + + + 0 + + + 20 + + + 15 + + + 0 + + + 0 + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + +