Core: Post-KivyMD cleanup 2 and enhancements (#4876)

* Adds a new class allowing TextFields to be resized
* Resizes most CommonClient components to be more in-line with pre-KivyMD
* Change the color of SelectableLabels and TooltipLabels to white
* Fixed ClientTabs not correctly showing the current tab indicator
* The server label now features a (i) icon to indicate that it can be hovered over.
* Changed the default `primary_palette` to `Lightsteelblue` and the default `dynamic_scheme_name` to `VIBRANT`
* Properly set attributes on `KivyJSONToTextParser.TextColors` so that proper typing can be utilized if an individual value is needed
* Fixed some buttons being discolored permanently once pressed
* Sped up the animations of button ripples and tab switching
* Added the ability to insert a new tab to `GameManager.add_client_tab`
* Hovering over the "Command" button in CommonClient will now display the contents of `/help` as a popup (note: this popup can be too large on default height for adequately large /help (SC2 Client), but should always fit fine on fullscreen).
* Fixed invalid sizing of MessageBox errors, and changed their text color to white
This commit is contained in:
Silvris
2025-04-15 17:09:27 -05:00
committed by GitHub
parent 1873c52aa6
commit 125bf6f270
4 changed files with 198 additions and 52 deletions

View File

@@ -359,6 +359,11 @@ def run_gui(path: str, args: Any) -> None:
self._refresh_components(self.current_filter) self._refresh_components(self.current_filter)
# Uncomment to re-enable the Kivy console/live editor
# Ctrl-E to enable it, make sure numlock/capslock is disabled
# from kivy.modules.console import create_console
# create_console(Window, self.top_screen)
return self.top_screen return self.top_screen
def on_start(self): def on_start(self):

View File

@@ -16,21 +16,30 @@
orange: "FF7700" # Used for command echo orange: "FF7700" # Used for command echo
# KivyMD theming parameters # KivyMD theming parameters
theme_style: "Dark" # Light/Dark theme_style: "Dark" # Light/Dark
primary_palette: "Green" # Many options primary_palette: "Lightsteelblue" # Many options
dynamic_scheme_name: "TONAL_SPOT" dynamic_scheme_name: "VIBRANT"
dynamic_scheme_contrast: 0.0 dynamic_scheme_contrast: 0.0
<MDLabel>: <MDLabel>:
color: self.theme_cls.primaryColor color: self.theme_cls.primaryColor
<BaseButton>:
ripple_color: app.theme_cls.primaryColor
ripple_duration_in_fast: 0.2
<MDTabsItemBase>:
ripple_color: app.theme_cls.primaryColor
ripple_duration_in_fast: 0.2
<TooltipLabel>: <TooltipLabel>:
adaptive_height: True adaptive_height: True
font_size: dp(20) theme_font_size: "Custom"
font_size: "20dp"
markup: True markup: True
halign: "left" halign: "left"
<SelectableLabel>: <SelectableLabel>:
size_hint: 1, None size_hint: 1, None
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
canvas.before: canvas.before:
Color: Color:
rgba: (.0, 0.9, .1, .3) if self.selected else self.theme_cls.surfaceContainerLowColor rgba: (self.theme_cls.primaryColor[0], self.theme_cls.primaryColor[1], self.theme_cls.primaryColor[2], .3) if self.selected else self.theme_cls.surfaceContainerLowestColor
Rectangle: Rectangle:
size: self.size size: self.size
pos: self.pos pos: self.pos
@@ -154,9 +163,12 @@
<ToolTip>: <ToolTip>:
size: self.texture_size size: self.texture_size
size_hint: None, None size_hint: None, None
theme_font_size: "Custom"
font_size: dp(18) font_size: dp(18)
pos_hint: {'center_y': 0.5, 'center_x': 0.5} pos_hint: {'center_y': 0.5, 'center_x': 0.5}
halign: "left" halign: "left"
theme_text_color: "Custom"
text_color: (1, 1, 1, 1)
canvas.before: canvas.before:
Color: Color:
rgba: 0.2, 0.2, 0.2, 1 rgba: 0.2, 0.2, 0.2, 1
@@ -175,11 +187,28 @@
rectangle: self.x-2, self.y-2, self.width+4, self.height+4 rectangle: self.x-2, self.y-2, self.width+4, self.height+4
<ServerToolTip>: <ServerToolTip>:
pos_hint: {'center_y': 0.5, 'center_x': 0.5} pos_hint: {'center_y': 0.5, 'center_x': 0.5}
<AutocompleteHintInput> <AutocompleteHintInput>:
size_hint_y: None size_hint_y: None
height: dp(30) height: "30dp"
multiline: False multiline: False
write_tab: False write_tab: False
pos_hint: {"center_x": 0.5, "center_y": 0.5}
<ConnectBarTextInput>:
height: "30dp"
multiline: False
write_tab: False
role: "medium"
size_hint_y: None
pos_hint: {"center_x": 0.5, "center_y": 0.5}
<CommandPromptTextInput>:
size_hint_y: None
height: "30dp"
multiline: False
write_tab: False
pos_hint: {"center_x": 0.5, "center_y": 0.5}
<MessageBoxLabel>:
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
<ScrollBox>: <ScrollBox>:
layout: layout layout: layout
bar_width: "12dp" bar_width: "12dp"

View File

@@ -5,12 +5,13 @@
size_hint: 1, None size_hint: 1, None
height: "75dp" height: "75dp"
context_button: context context_button: context
focus_behavior: False
MDRelativeLayout: MDRelativeLayout:
ApAsyncImage: ApAsyncImage:
source: main.image source: main.image
size: (48, 48) size: (48, 48)
size_hint_y: None size_hint: None, None
pos_hint: {"center_x": 0.1, "center_y": 0.5} pos_hint: {"center_x": 0.1, "center_y": 0.5}
MDLabel: MDLabel:
@@ -37,6 +38,7 @@
pos_hint:{"center_x": 0.85, "center_y": 0.8} pos_hint:{"center_x": 0.85, "center_y": 0.8}
theme_text_color: "Custom" theme_text_color: "Custom"
text_color: app.theme_cls.primaryColor text_color: app.theme_cls.primaryColor
detect_visible: False
on_release: app.set_favorite(self) on_release: app.set_favorite(self)
MDIconButton: MDIconButton:
@@ -46,6 +48,7 @@
pos_hint:{"center_x": 0.95, "center_y": 0.8} pos_hint:{"center_x": 0.95, "center_y": 0.8}
theme_text_color: "Custom" theme_text_color: "Custom"
text_color: app.theme_cls.primaryColor text_color: app.theme_cls.primaryColor
detect_visible: False
MDButton: MDButton:
pos_hint:{"center_x": 0.9, "center_y": 0.25} pos_hint:{"center_x": 0.9, "center_y": 0.25}
@@ -53,7 +56,7 @@
height: "25dp" height: "25dp"
component: main.component component: main.component
on_release: app.component_action(self) on_release: app.component_action(self)
detect_visible: False
MDButtonText: MDButtonText:
text: "Open" text: "Open"

185
kvui.py
View File

@@ -43,8 +43,8 @@ from kivy.core.image import ImageLoader, ImageLoaderBase, ImageData
from kivy.base import ExceptionHandler, ExceptionManager from kivy.base import ExceptionHandler, ExceptionManager
from kivy.clock import Clock from kivy.clock import Clock
from kivy.factory import Factory from kivy.factory import Factory
from kivy.properties import BooleanProperty, ObjectProperty, NumericProperty from kivy.properties import BooleanProperty, ObjectProperty, NumericProperty, StringProperty
from kivy.metrics import dp from kivy.metrics import dp, sp
from kivy.uix.widget import Widget from kivy.uix.widget import Widget
from kivy.uix.layout import Layout from kivy.uix.layout import Layout
from kivy.utils import escape_markup from kivy.utils import escape_markup
@@ -60,7 +60,7 @@ from kivymd.app import MDApp
from kivymd.uix.gridlayout import MDGridLayout from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.tab.tab import MDTabsPrimary, MDTabsItem, MDTabsItemText, MDTabsCarousel from kivymd.uix.tab.tab import MDTabsSecondary, MDTabsItem, MDTabsItemText, MDTabsCarousel
from kivymd.uix.menu import MDDropdownMenu from kivymd.uix.menu import MDDropdownMenu
from kivymd.uix.menu.menu import MDDropdownTextItem from kivymd.uix.menu.menu import MDDropdownTextItem
from kivymd.uix.dropdownitem import MDDropDownItem, MDDropDownItemText from kivymd.uix.dropdownitem import MDDropDownItem, MDDropDownItemText
@@ -90,10 +90,10 @@ remove_between_brackets = re.compile(r"\[.*?]")
class ThemedApp(MDApp): class ThemedApp(MDApp):
def set_colors(self): def set_colors(self):
text_colors = KivyJSONtoTextParser.TextColors() text_colors = KivyJSONtoTextParser.TextColors()
self.theme_cls.theme_style = getattr(text_colors, "theme_style", "Dark") self.theme_cls.theme_style = text_colors.theme_style
self.theme_cls.primary_palette = getattr(text_colors, "primary_palette", "Green") self.theme_cls.primary_palette = text_colors.primary_palette
self.theme_cls.dynamic_scheme_name = getattr(text_colors, "dynamic_scheme_name", "TONAL_SPOT") self.theme_cls.dynamic_scheme_name = text_colors.dynamic_scheme_name
self.theme_cls.dynamic_scheme_contrast = getattr(text_colors, "dynamic_scheme_contrast", 0.0) self.theme_cls.dynamic_scheme_contrast = text_colors.dynamic_scheme_contrast
class ImageIcon(MDButtonIcon, AsyncImage): class ImageIcon(MDButtonIcon, AsyncImage):
@@ -166,6 +166,32 @@ class ToggleButton(MDButton, ToggleButtonBehavior):
child.icon_color = self.theme_cls.primaryColor child.icon_color = self.theme_cls.primaryColor
# thanks kivymd
class ResizableTextField(MDTextField):
"""
Resizable MDTextField that manually overrides the builtin sizing.
Note that in order to use this, the sizing must be specified from within a .kv rule.
"""
def __init__(self, *args, **kwargs):
# cursed rules override
rules = Builder.match(self)
textfield = next((rule for rule in rules if rule.name == f"<MDTextField>"), None)
if textfield:
subclasses = rules[rules.index(textfield) + 1:]
for subclass in subclasses:
height_rule = subclass.properties.get("height", None)
if height_rule:
height_rule.ignore_prev = True
super().__init__(args, kwargs)
def on_release(self: MDButton, *args):
super(MDButton, self).on_release(args)
self.on_leave()
MDButton.on_release = on_release
# I was surprised to find this didn't already exist in kivy :( # I was surprised to find this didn't already exist in kivy :(
class HoverBehavior(object): class HoverBehavior(object):
"""originally from https://stackoverflow.com/a/605348110""" """originally from https://stackoverflow.com/a/605348110"""
@@ -266,11 +292,15 @@ class TooltipLabel(HovererableLabel, MDTooltip):
self._tooltip = None self._tooltip = None
class ServerLabel(HovererableLabel, MDTooltip): class ServerLabel(HoverBehavior, MDTooltip, MDBoxLayout):
tooltip_display_delay = 0.1 tooltip_display_delay = 0.1
text: str = StringProperty("Server:")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(HovererableLabel, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.add_widget(MDIcon(icon="information", font_size=sp(15)))
self.add_widget(TooltipLabel(text=self.text, pos_hint={"center_x": 0.5, "center_y": 0.5},
font_size=sp(15)))
self._tooltip = ServerToolTip(text="Test") self._tooltip = ServerToolTip(text="Test")
def on_enter(self): def on_enter(self):
@@ -383,7 +413,6 @@ class MarkupDropdownTextItem(MDDropdownTextItem):
for child in self.children: for child in self.children:
if child.__class__ == MDLabel: if child.__class__ == MDLabel:
child.markup = True child.markup = True
print(self.text)
# Currently, this only lets us do markup on text that does not have any icons # Currently, this only lets us do markup on text that does not have any icons
# Create new TextItems as needed # Create new TextItems as needed
@@ -461,14 +490,13 @@ class MarkupDropdown(MDDropdownMenu):
self.menu.data = self._items self.menu.data = self._items
class AutocompleteHintInput(MDTextField): class AutocompleteHintInput(ResizableTextField):
min_chars = NumericProperty(3) min_chars = NumericProperty(3)
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.dropdown = MarkupDropdown(caller=self, position="bottom", border_margin=dp(24), width=self.width) self.dropdown = MarkupDropdown(caller=self, position="bottom", border_margin=dp(2), width=self.width)
self.dropdown.bind(on_select=lambda instance, x: setattr(self, 'text', x))
self.bind(on_text_validate=self.on_message) self.bind(on_text_validate=self.on_message)
self.bind(width=lambda instance, x: setattr(self.dropdown, "width", x)) self.bind(width=lambda instance, x: setattr(self.dropdown, "width", x))
@@ -485,8 +513,11 @@ class AutocompleteHintInput(MDTextField):
def on_press(text): def on_press(text):
split_text = MarkupLabel(text=text).markup split_text = MarkupLabel(text=text).markup
return self.dropdown.select("".join(text_frag for text_frag in split_text self.set_text(self, "".join(text_frag for text_frag in split_text
if not text_frag.startswith("["))) if not text_frag.startswith("[")))
self.dropdown.dismiss()
self.focus = True
lowered = value.lower() lowered = value.lower()
for item_name in item_names: for item_name in item_names:
try: try:
@@ -498,7 +529,7 @@ class AutocompleteHintInput(MDTextField):
text = text[:index] + "[b]" + text[index:index+len(value)]+"[/b]"+text[index+len(value):] text = text[:index] + "[b]" + text[index:index+len(value)]+"[/b]"+text[index+len(value):]
self.dropdown.items.append({ self.dropdown.items.append({
"text": text, "text": text,
"on_release": lambda: on_press(text), "on_release": lambda txt=text: on_press(txt),
"markup": True "markup": True
}) })
if not self.dropdown.parent: if not self.dropdown.parent:
@@ -620,7 +651,7 @@ class HintLabel(RecycleDataViewBehavior, MDBoxLayout):
self.selected = is_selected self.selected = is_selected
class ConnectBarTextInput(MDTextField): class ConnectBarTextInput(ResizableTextField):
def insert_text(self, substring, from_undo=False): def insert_text(self, substring, from_undo=False):
s = substring.replace("\n", "").replace("\r", "") s = substring.replace("\n", "").replace("\r", "")
return super(ConnectBarTextInput, self).insert_text(s, from_undo=from_undo) return super(ConnectBarTextInput, self).insert_text(s, from_undo=from_undo)
@@ -630,7 +661,7 @@ def is_command_input(string: str) -> bool:
return len(string) > 0 and string[0] in "/!" return len(string) > 0 and string[0] in "/!"
class CommandPromptTextInput(MDTextField): class CommandPromptTextInput(ResizableTextField):
MAXIMUM_HISTORY_MESSAGES = 50 MAXIMUM_HISTORY_MESSAGES = 50
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
@@ -682,29 +713,61 @@ class MessageBox(Popup):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self._label.refresh() self._label.refresh()
self.size = self._label.texture.size
if self.width + 50 > Window.width:
self.text_size[0] = Window.width - 50
self._label.refresh()
self.size = self._label.texture.size
def __init__(self, title, text, error=False, **kwargs): def __init__(self, title, text, error=False, **kwargs):
label = MessageBox.MessageBoxLabel(text=text) label = MessageBox.MessageBoxLabel(text=text)
separator_color = [217 / 255, 129 / 255, 122 / 255, 1.] if error else [47 / 255., 167 / 255., 212 / 255, 1.] separator_color = [217 / 255, 129 / 255, 122 / 255, 1.] if error else [47 / 255., 167 / 255., 212 / 255, 1.]
super().__init__(title=title, content=label, size_hint=(None, None), width=max(100, int(label.width) + 40), super().__init__(title=title, content=label, size_hint=(0.5, None), width=max(100, int(label.width) + 40),
separator_color=separator_color, **kwargs) separator_color=separator_color, **kwargs)
self.height += max(0, label.height - 18) self.height += max(0, label.height - 18)
class ClientTabs(MDTabsPrimary): class ClientTabs(MDTabsSecondary):
carousel: MDTabsCarousel carousel: MDTabsCarousel
lock_swiping = True lock_swiping = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.carousel = MDTabsCarousel(lock_swiping=True) self.carousel = MDTabsCarousel(lock_swiping=True, anim_move_duration=0.2)
super().__init__(*args, MDDivider(size_hint_y=None, height=dp(4)), self.carousel, **kwargs) super().__init__(*args, MDDivider(size_hint_y=None, height=dp(1)), self.carousel, **kwargs)
self.size_hint_y = 1 self.size_hint_y = 1
def _check_panel_height(self, *args):
self.ids.tab_scroll.height = dp(38)
def update_indicator(
self, x: float = 0.0, w: float = 0.0, instance: MDTabsItem = None
) -> None:
def update_indicator(*args):
indicator_pos = (0, 0)
indicator_size = (0, 0)
item_text_object = self._get_tab_item_text_icon_object()
if item_text_object:
indicator_pos = (
instance.x + dp(12),
self.indicator.pos[1]
if not self._tabs_carousel
else self._tabs_carousel.height,
)
indicator_size = (
instance.width - dp(24),
self.indicator_height,
)
Animation(
pos=indicator_pos,
size=indicator_size,
d=0 if not self.indicator_anim else self.indicator_duration,
t=self.indicator_transition,
).start(self.indicator)
if not instance:
self.indicator.pos = (x, self.indicator.pos[1])
self.indicator.size = (w, self.indicator_height)
else:
Clock.schedule_once(update_indicator)
def remove_tab(self, tab, content=None): def remove_tab(self, tab, content=None):
if content is None: if content is None:
content = tab.content content = tab.content
@@ -713,6 +776,21 @@ class ClientTabs(MDTabsPrimary):
self.on_size(self, self.size) self.on_size(self, self.size)
class CommandButton(MDButton, MDTooltip):
def __init__(self, *args, manager: "GameManager", **kwargs):
super().__init__(*args, **kwargs)
self.manager = manager
self._tooltip = ToolTip(text="Test")
def on_enter(self):
self._tooltip.text = self.manager.commandprocessor.get_help_text()
self._tooltip.font_size = dp(20 - (len(self._tooltip.text) // 400)) # mostly guessing on the numbers here
self.display_tooltip()
def on_leave(self):
self.animation_tooltip_dismiss()
class GameManager(ThemedApp): class GameManager(ThemedApp):
logging_pairs = [ logging_pairs = [
("Client", "Archipelago"), ("Client", "Archipelago"),
@@ -767,19 +845,19 @@ class GameManager(ThemedApp):
self.grid = MainLayout() self.grid = MainLayout()
self.grid.cols = 1 self.grid.cols = 1
self.connect_layout = MDBoxLayout(orientation="horizontal", size_hint_y=None, height=dp(70), self.connect_layout = MDBoxLayout(orientation="horizontal", size_hint_y=None, height=dp(40),
spacing=5, padding=(5, 10)) spacing=5, padding=(5, 10))
# top part # top part
server_label = ServerLabel(halign="center") server_label = ServerLabel(width=dp(75))
self.connect_layout.add_widget(server_label) self.connect_layout.add_widget(server_label)
self.server_connect_bar = ConnectBarTextInput(text=self.ctx.suggested_address or "archipelago.gg:", self.server_connect_bar = ConnectBarTextInput(text=self.ctx.suggested_address or "archipelago.gg:",
size_hint_y=None, role="medium", pos_hint={"center_x": 0.5, "center_y": 0.5})
height=dp(70), multiline=False, write_tab=False)
def connect_bar_validate(sender): def connect_bar_validate(sender):
if not self.ctx.server: if not self.ctx.server:
self.connect_button_action(sender) self.connect_button_action(sender)
self.server_connect_bar.height = dp(30)
self.server_connect_bar.bind(on_text_validate=connect_bar_validate) self.server_connect_bar.bind(on_text_validate=connect_bar_validate)
self.connect_layout.add_widget(self.server_connect_bar) self.connect_layout.add_widget(self.server_connect_bar)
self.server_connect_button = MDButton(MDButtonText(text="Connect"), style="filled", size=(dp(100), dp(70)), self.server_connect_button = MDButton(MDButtonText(text="Connect"), style="filled", size=(dp(100), dp(70)),
@@ -792,7 +870,7 @@ class GameManager(ThemedApp):
self.grid.add_widget(self.progressbar) self.grid.add_widget(self.progressbar)
# middle part # middle part
self.tabs = ClientTabs() self.tabs = ClientTabs(pos_hint={"center_x": 0.5, "center_y": 0.5})
self.tabs.add_widget(MDTabsItem(MDTabsItemText(text="All" if len(self.logging_pairs) > 1 else "Archipelago"))) self.tabs.add_widget(MDTabsItem(MDTabsItemText(text="All" if len(self.logging_pairs) > 1 else "Archipelago")))
self.log_panels["All"] = self.tabs.default_tab_content = UILog(*(logging.getLogger(logger_name) self.log_panels["All"] = self.tabs.default_tab_content = UILog(*(logging.getLogger(logger_name)
for logger_name, name in for logger_name, name in
@@ -820,9 +898,10 @@ class GameManager(ThemedApp):
self.grid.add_widget(self.main_area_container) self.grid.add_widget(self.main_area_container)
# bottom part # bottom part
bottom_layout = MDBoxLayout(orientation="horizontal", size_hint_y=None, height=dp(70), spacing=5, padding=(5, 10)) bottom_layout = MDBoxLayout(orientation="horizontal", size_hint_y=None, height=dp(40), spacing=5, padding=(5, 10))
info_button = MDButton(MDButtonText(text="Command:"), radius=5, style="filled", size=(dp(100), dp(70)), info_button = CommandButton(MDButtonText(text="Command:", halign="left"), manager=self, radius=5,
size_hint_x=None, size_hint_y=None, pos_hint={"center_y": 0.575}) style="filled", size=(dp(100), dp(70)), size_hint_x=None, size_hint_y=None,
pos_hint={"center_y": 0.575})
info_button.bind(on_release=self.command_button_action) info_button.bind(on_release=self.command_button_action)
bottom_layout.add_widget(info_button) bottom_layout.add_widget(info_button)
self.textinput = CommandPromptTextInput(size_hint_y=None, height=dp(30), multiline=False, write_tab=False) self.textinput = CommandPromptTextInput(size_hint_y=None, height=dp(30), multiline=False, write_tab=False)
@@ -843,13 +922,25 @@ class GameManager(ThemedApp):
self.server_connect_bar.focus = True self.server_connect_bar.focus = True
self.server_connect_bar.select_text(port_start if port_start > 0 else host_start, len(s)) self.server_connect_bar.select_text(port_start if port_start > 0 else host_start, len(s))
# Uncomment to enable the kivy live editor console
# Press Ctrl-E (with numlock/capslock) disabled to open
# from kivy.core.window import Window
# from kivy.modules import console
# console.create_console(Window, self.container)
return self.container return self.container
def add_client_tab(self, title: str, content: Widget) -> Widget: def add_client_tab(self, title: str, content: Widget, index: int = -1) -> Widget:
"""Adds a new tab to the client window with a given title, and provides a given Widget as its content. """Adds a new tab to the client window with a given title, and provides a given Widget as its content.
Returns the new tab widget, with the provided content being placed on the tab as content.""" Returns the new tab widget, with the provided content being placed on the tab as content."""
new_tab = MDTabsItem(MDTabsItemText(text=title)) new_tab = MDTabsItem(MDTabsItemText(text=title))
new_tab.content = content new_tab.content = content
if -1 < index <= len(self.tabs.carousel.slides):
new_tab.bind(on_release=self.tabs.set_active_item)
new_tab._tabs = self.tabs
self.tabs.ids.container.add_widget(new_tab, index=index)
self.tabs.carousel.add_widget(new_tab.content, index=len(self.tabs.carousel.slides) - index)
else:
self.tabs.add_widget(new_tab) self.tabs.add_widget(new_tab)
self.tabs.carousel.add_widget(new_tab.content) self.tabs.carousel.add_widget(new_tab.content)
return new_tab return new_tab
@@ -1001,8 +1092,9 @@ class HintLayout(MDBoxLayout):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
boxlayout = MDBoxLayout(orientation="horizontal", size_hint_y=None, height=dp(55)) boxlayout = MDBoxLayout(orientation="horizontal", size_hint_y=None, height=dp(40))
boxlayout.add_widget(MDLabel(text="New Hint:", size_hint_x=None, size_hint_y=None, height=dp(55))) boxlayout.add_widget(MDLabel(text="New Hint:", size_hint_x=None, size_hint_y=None,
height=dp(40), width=dp(75), halign="center", valign="center"))
boxlayout.add_widget(AutocompleteHintInput()) boxlayout.add_widget(AutocompleteHintInput())
self.add_widget(boxlayout) self.add_widget(boxlayout)
@@ -1109,6 +1201,7 @@ class HintLog(MDRecycleView):
class ApAsyncImage(AsyncImage): class ApAsyncImage(AsyncImage):
def is_uri(self, filename: str) -> bool: def is_uri(self, filename: str) -> bool:
if filename.startswith("ap:"): if filename.startswith("ap:"):
return True return True
@@ -1154,7 +1247,23 @@ class E(ExceptionHandler):
class KivyJSONtoTextParser(JSONtoTextParser): class KivyJSONtoTextParser(JSONtoTextParser):
# dummy class to absorb kvlang definitions # dummy class to absorb kvlang definitions
class TextColors(Widget): class TextColors(Widget):
pass white: str = StringProperty("FFFFFF")
black: str = StringProperty("000000")
red: str = StringProperty("EE0000")
green: str = StringProperty("00FF7F")
yellow: str = StringProperty("FAFAD2")
blue: str = StringProperty("6495ED")
magenta: str = StringProperty("EE00EE")
cyan: str = StringProperty("00EEEE")
slateblue: str = StringProperty("6D8BE8")
plum: str = StringProperty("AF99EF")
salmon: str = StringProperty("FA8072")
orange: str = StringProperty("FF7700")
# KivyMD parameters
theme_style: str = StringProperty("Dark")
primary_palette: str = StringProperty("Lightsteelblue")
dynamic_scheme_name: str = StringProperty("VIBRANT")
dynamic_scheme_contrast: int = NumericProperty(0)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# we grab the color definitions from the .kv file, then overwrite the JSONtoTextParser default entries # we grab the color definitions from the .kv file, then overwrite the JSONtoTextParser default entries