diff --git a/OSCRUI/app.py b/OSCRUI/app.py index dae26c8..b985e35 100644 --- a/OSCRUI/app.py +++ b/OSCRUI/app.py @@ -5,6 +5,7 @@ from PySide6.QtWidgets import QApplication, QWidget, QLineEdit, QFrame, QListWidget, QTabWidget, QTableView from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QGridLayout from PySide6.QtCore import QSize, QSettings +from PySide6.QtGui import QIntValidator from OSCR import TREE_HEADER, HEAL_TREE_HEADER @@ -26,7 +27,7 @@ class OSCRUI(): from .style import get_style_class, create_style_sheet, theme_font, get_style from .widgetbuilder import create_frame, create_label, create_button_series, create_icon_button from .widgetbuilder import create_analysis_table, create_button, create_combo_box, style_table - from .widgetbuilder import split_dialog + from .widgetbuilder import split_dialog, create_entry from .leagueconnector import upload_callback, update_ladder_index, establish_league_connection app_dir = None @@ -98,7 +99,7 @@ def init_settings(self): """ Prepares settings. Loads stored settings. Saves current settings for next startup. """ - settings_path = os.path.abspath(f'{self.app_dir}/{self.config["settings_path"]}') + settings_path = os.path.abspath(self.app_dir + self.config["settings_path"]) self.settings = QSettings(settings_path, QSettings.Format.IniFormat) for setting, value in self.config['default_settings'].items(): if self.settings.value(setting, None) is None: @@ -112,6 +113,8 @@ def init_config(self): """ self.current_combat_id = -1 self.current_combat_path = '' + self.config['templog_folder_path'] = os.path.abspath( + self.app_dir + self.config['templog_folder_path']) @property def parser_settings(self) -> dict: @@ -119,12 +122,13 @@ def parser_settings(self) -> dict: Returns settings relevant to the parser """ relevant_settings = (('combats_to_parse', int), ('seconds_between_combats', int), - ('excluded_event_ids', list), ('graph_resolution', float), ('templog_folder_path', str)) + ('excluded_event_ids', list), ('graph_resolution', float)) settings = dict() for setting_key, settings_type in relevant_settings: setting = self.settings.value(setting_key, type=settings_type, defaultValue='') if setting: settings[setting_key] = setting + settings['templog_folder_path'] = self.config['templog_folder_path'] return settings @@ -248,13 +252,14 @@ def setup_left_sidebar(self, frame:QFrame): self.entry = QLineEdit(self.settings.value('log_path', ''), frame) self.entry.setStyleSheet(self.get_style_class('QLineEdit', 'entry')) + self.entry.setFont(self.theme_font('entry')) self.entry.setFixedWidth(self.sidebar_item_width) left_layout.addWidget(self.entry) entry_button_config = { 'default': {'margin-bottom': '@isp'}, 'Browse ...': {'callback': lambda: self.browse_log(self.entry), 'align': ALEFT}, - 'Analyze': {'callback': lambda: self.analyze_log_callback(path=self.entry.text(), parser_num=1), + 'Scan': {'callback': lambda: self.analyze_log_callback(path=self.entry.text(), parser_num=1), 'align': ARIGHT} } entry_buttons = self.create_button_series(frame, entry_button_config, 'button') @@ -389,13 +394,15 @@ def setup_overview_frame(self): switch_style = { 'default': {'margin-left': '@margin', 'margin-right': '@margin'}, - 'DPS Bar': {'callback': lambda: o_tabber.setCurrentIndex(0), 'align':ACENTER}, - 'DPS Graph': {'callback': lambda: o_tabber.setCurrentIndex(1), 'align':ACENTER}, - 'Damage Graph': {'callback': lambda: o_tabber.setCurrentIndex(2), 'align':ACENTER}, - 'Copy Summary': {'callback': self.copy_summary_callback, 'align':ACENTER}, - 'Upload Result': {'callback': self.upload_callback, 'align':ACENTER}, + 'DPS Bar': {'callback': lambda: self.switch_overview_tab(0), 'align': ACENTER, 'toggle': True}, + 'DPS Graph': {'callback': lambda: self.switch_overview_tab(1), 'align': ACENTER, 'toggle': False}, + 'Damage Graph': {'callback': lambda: self.switch_overview_tab(2), 'align': ACENTER, + 'toggle': False} } - switcher, buttons = self.create_button_series(switch_frame, switch_style, 'button', ret=True) + # 'Copy Summary': {'callback': self.copy_summary_callback, 'align':ACENTER}, + # 'Upload Result': {'callback': self.upload_callback, 'align':ACENTER}, + # } + switcher, buttons = self.create_button_series(switch_frame, switch_style, 'tab_button', ret=True) switcher.setContentsMargins(0, self.theme['defaults']['margin'], 0, 0) self.widgets.overview_menu_buttons = buttons switch_frame.setLayout(switcher) @@ -431,12 +438,14 @@ def setup_analysis_frame(self): switch_style = { 'default': {'margin-left': '@margin', 'margin-right': '@margin'}, - 'Damage Out': {'callback': lambda: a_tabber.setCurrentIndex(0), 'align':ACENTER}, - 'Damage Taken': {'callback': lambda: a_tabber.setCurrentIndex(1), 'align':ACENTER}, - 'Heals Out': {'callback': lambda: a_tabber.setCurrentIndex(2), 'align':ACENTER}, - 'Heals In': {'callback': lambda: a_tabber.setCurrentIndex(3), 'align':ACENTER} + 'Damage Out': {'callback': lambda: self.switch_analysis_tab(0), 'align': ACENTER, 'toggle': True}, + 'Damage Taken': {'callback': lambda: self.switch_analysis_tab(1), 'align': ACENTER, + 'toggle': False}, + 'Heals Out': {'callback': lambda: self.switch_analysis_tab(2), 'align': ACENTER, 'toggle': False}, + 'Heals In': {'callback': lambda: self.switch_analysis_tab(3), 'align': ACENTER, 'toggle': False} } - switcher, buttons = self.create_button_series(switch_frame, switch_style, 'button', ret=True) + switcher, buttons = self.create_button_series(switch_frame, switch_style, 'tab_button', ret=True) + # buttons[0].setEnabled(False) switcher.setContentsMargins(0, self.theme['defaults']['margin'], 0, 0) self.widgets.analysis_menu_buttons = buttons switch_frame.setLayout(switcher) @@ -595,8 +604,9 @@ def setup_settings_frame(self): """ settings_frame = self.widgets.main_tab_frames[3] settings_layout = QHBoxLayout() - settings_layout.setContentsMargins(0, 0, 0, 0) - settings_layout.setSpacing(0) + isp = self.theme['defaults']['isp'] + settings_layout.setContentsMargins(2 * isp, isp, isp, isp) + settings_layout.setSpacing(isp) col_1_frame = self.create_frame(settings_frame) col_1_frame.setSizePolicy(SMINMAX) @@ -608,35 +618,41 @@ def setup_settings_frame(self): col_3_frame.setSizePolicy(SMINMAX) settings_layout.addWidget(col_3_frame, alignment=ATOP, stretch=1) - col_1 = QVBoxLayout() - col_1.setSpacing(0) - dmg_hider_label = self.create_label('Damage table columns:', 'label', col_1_frame) - col_1.addWidget(dmg_hider_label) + # first column + col_1 = QHBoxLayout() + col_1.setContentsMargins(0, 0, 0, 0) + col_1.setSpacing(self.theme['defaults']['isp']) + col_1_1 = QVBoxLayout() + col_1_1.setSpacing(0) + dmg_hider_label = self.create_label('Damage table columns:', 'label_subhead') + col_1_1.addWidget(dmg_hider_label) dmg_hider_layout = QVBoxLayout() - dmg_hider_frame = self.create_frame(col_1_frame, style_override= + dmg_hider_frame = self.create_frame(col_1_frame, size_policy=SMINMAX, style_override= {'border-color':'@lbg', 'border-width':'@bw', 'border-style':'solid', 'border-radius': 2}) - dmg_hider_frame.setSizePolicy(SMINMAX) for i, head in enumerate(TREE_HEADER[1:]): bt = self.create_button(head, 'toggle_button', dmg_hider_frame, toggle=True) bt.setSizePolicy(SMINMAX) bt.setChecked(self.settings.value(f'dmg_columns|{i}', type=bool)) bt.clicked.connect(lambda state, i=i: self.settings.setValue(f'dmg_columns|{i}', state)) dmg_hider_layout.addWidget(bt, stretch=1) - dmg_hider_frame.setLayout(dmg_hider_layout) - col_1.addWidget(dmg_hider_frame, alignment=ATOP) - apply_button = self.create_button('Apply', 'button', col_1_frame, {'margin-top':15}) + dmg_seperator = self.create_frame(dmg_hider_frame, 'hr', style_override={'background-color': '@lbg'}, + size_policy=SMINMIN) + dmg_seperator.setFixedHeight(self.theme['defaults']['bw']) + dmg_hider_layout.addWidget(dmg_seperator) + apply_button = self.create_button('Apply', 'button', dmg_hider_frame) apply_button.clicked.connect(self.update_shown_columns_dmg) - col_1.addWidget(apply_button, alignment=ALEFT) - col_1_frame.setLayout(col_1) + dmg_hider_layout.addWidget(apply_button, alignment=ARIGHT|ATOP) + dmg_hider_frame.setLayout(dmg_hider_layout) + col_1_1.addWidget(dmg_hider_frame, alignment=ATOP) + col_1.addLayout(col_1_1, stretch=1) - col_2 = QVBoxLayout() - col_2.setSpacing(0) - heal_hider_label = self.create_label('Heal table columns:', 'label', col_2_frame) - col_2.addWidget(heal_hider_label) + col_1_2 = QVBoxLayout() + col_1_2.setSpacing(0) + heal_hider_label = self.create_label('Heal table columns:', 'label_subhead', col_1_frame) + col_1_2.addWidget(heal_hider_label) heal_hider_layout = QVBoxLayout() - heal_hider_frame = self.create_frame(col_2_frame, style_override= + heal_hider_frame = self.create_frame(col_1_frame, size_policy=SMINMAX, style_override= {'border-color':'@lbg', 'border-width':'@bw', 'border-style':'solid', 'border-radius': 2}) - heal_hider_frame.setSizePolicy(SMINMAX) for i, head in enumerate(HEAL_TREE_HEADER[1:]): bt = self.create_button(head, 'toggle_button', heal_hider_frame) bt.setCheckable(True) @@ -644,11 +660,33 @@ def setup_settings_frame(self): bt.setChecked(self.settings.value(f'heal_columns|{i}', type=bool)) bt.clicked.connect(lambda state, i=i: self.settings.setValue(f'heal_columns|{i}', state)) heal_hider_layout.addWidget(bt, stretch=1) - heal_hider_frame.setLayout(heal_hider_layout) - col_2.addWidget(heal_hider_frame, alignment=ATOP) - apply_button_2 = self.create_button('Apply', 'button', col_2_frame, {'margin-top':15}) + heal_seperator = self.create_frame(dmg_hider_frame, 'hr', style_override={'background-color': '@lbg'}, + size_policy=SMINMIN) + heal_seperator.setFixedHeight(self.theme['defaults']['bw']) + heal_hider_layout.addWidget(heal_seperator) + apply_button_2 = self.create_button('Apply', 'button', heal_hider_frame) apply_button_2.clicked.connect(self.update_shown_columns_heal) - col_2.addWidget(apply_button_2, alignment=ALEFT) + heal_hider_layout.addWidget(apply_button_2, alignment=ARIGHT|ATOP) + heal_hider_frame.setLayout(heal_hider_layout) + col_1_2.addWidget(heal_hider_frame, alignment=ATOP) + col_1.addLayout(col_1_2, stretch=1) + + col_1_frame.setLayout(col_1) + + # second column + col_2 = QGridLayout() + col_2.setContentsMargins(0, 0, 0, 0) + col_2.setVerticalSpacing(self.theme['defaults']['isp']) + col_2.setHorizontalSpacing(self.theme['defaults']['csp']) + combat_delta_label = self.create_label('Seconds Between Combats:', 'label_subhead') + col_2.addWidget(combat_delta_label, 0, 0, alignment=ARIGHT) + combat_delta_validator = QIntValidator() + combat_delta_validator.setBottom(1) + combat_delta_entry = self.create_entry(self.settings.value('seconds_between_combats', + type=str), combat_delta_validator, style_override={'margin-top': 0}) + combat_delta_entry.editingFinished.connect(lambda: self.settings.setValue('seconds_between_combats', + combat_delta_entry.text())) + col_2.addWidget(combat_delta_entry, 0, 1, alignment=ALEFT) col_2_frame.setLayout(col_2) settings_frame.setLayout(settings_layout) @@ -702,6 +740,33 @@ def navigate_log(self, direction: str): self.widgets.navigate_up_button.setEnabled(self.parser1.navigation_up) self.widgets.navigate_down_button.setEnabled(self.parser1.navigation_down) + def switch_analysis_tab(self, tab_index: int): + """ + Callback for tab switch buttons; switches tab and sets active button. + + Parameters: + - :param tab_index: index of the tab to switch to + """ + self.widgets.analysis_tabber.setCurrentIndex(tab_index) + for index, button in enumerate(self.widgets.analysis_menu_buttons): + if not index == tab_index: + button.setChecked(False) + else: + button.setChecked(True) + + def switch_overview_tab(self, tab_index: int): + """ + Callback for tab switch buttons; switches tab and sets active button. + + Parameters: + - :param tab_index: index of the tab to switch to + """ + self.widgets.overview_tabber.setCurrentIndex(tab_index) + for index, button in enumerate(self.widgets.overview_menu_buttons): + if not index == tab_index: + button.setChecked(False) + else: + button.setChecked(True) def set_variable(self, var_to_be_set, index, value): """ diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py index 494c857..5793cb5 100644 --- a/OSCRUI/widgetbuilder.py +++ b/OSCRUI/widgetbuilder.py @@ -187,7 +187,7 @@ def create_button_series(self, parent, buttons:dict, style, shape:str='row', sep if ret: return layout, button_list else: return layout -def create_combo_box(self, parent, style:str = 'combobox', style_override: dict = {}) -> QComboBox: +def create_combo_box(self, parent, style: str = 'combobox', style_override: dict = {}) -> QComboBox: """ Creates a combobox with given style and returns it. @@ -207,6 +207,29 @@ def create_combo_box(self, parent, style:str = 'combobox', style_override: dict combo_box.setSizePolicy(SMINMAX) return combo_box +def create_entry(self, default_value, validator=None, style: str = 'entry', style_override: dict = {} + ) -> QLineEdit: + """ + Creates an entry widget and styles it. + + Parameters: + - :param default_value: default value for the entry + - :param validator: validator to validate entered characters against + - :param style: key for self.theme -> default style + - :param style_override: style dict to override default style + + :return: styled QLineEdit + """ + entry = QLineEdit(default_value) + entry.setValidator(validator) + entry.setStyleSheet(get_style_class(self, 'QLineEdit', style, style_override)) + if 'font' in style_override: + entry.setFont(theme_font(self, style, style_override['font'])) + else: + entry.setFont(theme_font(self, style)) + entry.setSizePolicy(SMAXMAX) + return entry + def resize_tree_table(tree: QTreeView): """ Resizes the columns of the given tree table to fit its contents @@ -347,7 +370,7 @@ def split_dialog(self): auto_split_heading = create_label(self, 'Split Log Automatically:', 'label_heading') grid_layout.addWidget(auto_split_heading, 0, 0, alignment=ALEFT) label_text = ('Automatically splits the logfile at the next combat end after ' - f'{self.settings.value("split_log_after", type=int):,} lines until the entire file has been split. ' + f'{self.settings.value('split_log_after', type=int):,} lines until the entire file has been split. ' 'The new files are written to the selected folder. It is advised to select an empty folder ' 'to ensure all files are saved correctly.') auto_split_text = create_label(self, label_text, 'label') diff --git a/main.py b/main.py index c103311..4ccf1ea 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ class Launcher(): - version = '2024.2a290' + version = '2024.3a10' # holds the style of the app theme = { @@ -14,10 +14,10 @@ class Launcher(): 'bg': '#1a1a1a', 'fg': '#eeeeee', 'oscr': '#c82934', - 'font': ('Overpass', 15, 'normal'), # used when no font is specified in style definition - 'heading': ('Overpass', 15, 'bold'), - 'text': ('Overpass', 13, 'medium'), - 'subhead': ('Overpass', 12, 'normal'), + 'font': ('Overpass', 11, 'normal'), # used when no font is specified in style definition + 'heading': ('Overpass', 14, 'bold'), + 'text': ('Overpass', 11, 'normal'), + 'subhead': ('Overpass', 12, 'medium'), 'font-fallback': ('Yu Gothic UI', 'Nirmala UI', 'Microsoft YaHei UI', 'sans-serif'), 'frame_thickness': 8, # this styles every item of the given type @@ -30,10 +30,10 @@ class Launcher(): 'margin': 0 }, 'QScrollBar:vertical': { - 'width': 10, + 'width': 8, }, 'QScrollBar:horizontal': { - 'height': 10, + 'height': 8, }, # space above and below the scrollbar handle 'QScrollBar::add-page, QScrollBar::sub-page': { @@ -41,8 +41,8 @@ class Launcher(): }, # scroll bar handle 'QScrollBar::handle': { - 'background-color': 'rgba(0,0,0,.75)', - 'border-radius': 5, + 'background-color': 'rgba(100,100,100,.75)', + 'border-radius': 4, 'border': 'none' }, # scroll bar arrow buttons @@ -62,7 +62,7 @@ class Launcher(): 'lbg': '#404040', # light background 'oscr': '#c82934', # accent 'loscr': '#20c82934', # light accent (12.5% opacity) - 'font': ('Overpass', 15, 'normal'), + 'font': ('Overpass', 11, 'normal'), 'fg': '#eeeeee', # foreground (usually text) 'mfg': '#bbbbbb', # medium foreground 'bc': '#888888', # border color @@ -98,21 +98,22 @@ class Launcher(): 'margin': (3, 0, 3, 0), 'qproperty-indent': '0', # disables auto-indent 'border-style': 'none', - 'font': ('Overpass', 12, 'normal') + 'font': ('Overpass', 11, 'normal') }, # heading label 'label_heading': { 'color': '@fg', 'qproperty-indent': '0', 'border-style': 'none', - 'font': ('Overpass', 15, 'bold') + 'font': ('Overpass', 14, 'bold') }, # label for subheading 'label_subhead': { 'color': '@fg', 'qproperty-indent': '0', 'border-style': 'none', - 'font': ('Overpass', 13, 'medium') + 'margin-bottom': 3, + 'font': ('Overpass', 12, 'medium') }, # default button 'button': { @@ -123,7 +124,7 @@ class Launcher(): 'border-radius': '@br', 'margin': (3, 3, 3, 3), 'padding': (2, 5, 0, 5), - 'font': ('Overpass', 15, 'medium'), + 'font': ('Overpass', 13, 'medium'), ':hover': { 'color': '@fg', 'border-width': '@bw', @@ -132,7 +133,27 @@ class Launcher(): }, ':disabled': { 'color': '@bc' + } + }, + # button for tab switching + 'tab_button': { + 'background': 'none', + 'color': '@fg', + 'text-decoration': 'none', + 'border-style': 'none', + 'border-width': '@bw', + 'border-color': '@oscr', + 'border-radius': '@br', + 'margin': (3, 3, 3, 3), + 'padding': (2, 5, 0, 5), + 'font': ('Overpass', 15, 'medium'), + ':hover': { + 'color': '@fg', + 'border-style': 'solid', }, + ':checked': { + 'border-style': 'solid', + } }, # big button (main tab switcher) 'menu_button': { @@ -202,7 +223,7 @@ class Launcher(): 'border-color': '@bc', 'border-radius': '@br', 'margin-top': '@csp', - 'font': ('Overpass', 12, 'normal'), + 'font': ('Overpass', 10, 'normal'), ':focus': { # cursor is inside the line 'border-color': '@oscr' } @@ -273,7 +294,7 @@ class Launcher(): 'border-radius': '@br', 'margin': (3, 3, 3, 3), 'padding': (2, 5, 0, 5), - 'font': ('Overpass', 15, 'medium'), + 'font': ('Overpass', 13, 'medium'), ':hover': { 'background-color': '@loscr', }, @@ -294,7 +315,7 @@ class Launcher(): 'gridline-color': 'rgba(0,0,0,0)', # -> s.c: table_gridline 'outline': '0', # removes dotted line around clicked item 'margin': (0, 10, 10, 0), - 'font': ('Roboto Mono', 15, 'Medium'), + 'font': ('Roboto Mono', 12, 'Medium'), '::item': { 'padding': (0, 5, 0, 5), 'border-width': '@bw', @@ -344,7 +365,7 @@ class Launcher(): 'border-bottom-style': 'solid', 'border-bottom-color': '@bc', 'outline': '0', # removes dotted line around clicked item - 'font': ('Overpass', 15, 'Medium'), + 'font': ('Overpass', 12, 'Medium'), '::section': { 'background-color': '@mbg', 'color': '@fg', @@ -384,9 +405,9 @@ class Launcher(): 'color': '@fg', 'margin': (10, 0, 10, 0), 'outline': '0', # removes dotted line around clicked item - 'font': ('Overpass', 11, 'Normal'), + 'font': ('Overpass', 12, 'Normal'), '::item': { - 'font': ('Roboto Mono', 11, 'Normal'), + 'font': ('Roboto Mono', 12, 'Normal'), 'border-right-width': '@bw', 'border-right-style': 'solid', 'border-right-color': '@bc', @@ -446,7 +467,7 @@ class Launcher(): 'background-color': '@bg', 'padding': (2, 5, 0, 5), 'color': '@fg', - 'font': ('Overpass', 12, 'normal'), + 'font': ('Overpass', 12, 'medium'), '::down-arrow': { 'image': 'url(assets/fat-arrow-down.svg)', 'width': '@margin', @@ -498,7 +519,7 @@ class Launcher(): '#B45492', '#A27534', '#54A9B4', '#E47B1C', '#BCBCBC'), }, 'plot_legend': { - 'font': ('Overpass', 10, 'Medium'), + 'font': ('Overpass', 11, 'Medium'), 'border-style': 'none', 'padding': 0, 'margin': 0 @@ -527,6 +548,7 @@ def app_config() -> dict: 'minimum_window_width': 1280, 'minimum_window_height': 720, 'settings_path': r'/.OSCR_settings.ini', + 'templog_folder_path': r'/~temp_log_files', 'default_settings': { 'log_path': '', 'geometry': None, @@ -569,7 +591,6 @@ def app_config() -> dict: 'split_log_after': 480000, 'seconds_between_combats': 100, 'excluded_event_ids': ['Autodesc.Combatevent.Falling', ''], - 'templog_folder_path': '', 'graph_resolution': 0.2, 'combats_to_parse': 10 }