pre collapse tab resize
This commit is contained in:
1 parent
bb0d0c9e57
commit
9adc381d5b
17 files changed
+163
-70
No files matched your search
@@ -0,0 +1,45 @@
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.textfield import MDTextField
|
||||
|
||||
|
||||
class MyMDTextField(MDTextField):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Clock.schedule_once(lambda dt: self.check_error(self.text), 0)
|
||||
|
||||
def check_error(self, text):
|
||||
self.error = text != "correct"
|
||||
print("checking error for", text, "->", self.error)
|
||||
|
||||
def on_text(self, widget, text):
|
||||
self.check_error(text)
|
||||
return super().on_text(widget, text)
|
||||
|
||||
|
||||
KV = """
|
||||
BoxLayout:
|
||||
padding: "10dp"
|
||||
|
||||
MyMDTextField:
|
||||
id: text_field_error
|
||||
text: 'incorrect'
|
||||
hint_text: "Helper text on error (press 'Enter')"
|
||||
helper_text: "There will always be a mistake"
|
||||
helper_text_mode: "on_error"
|
||||
pos_hint: {"center_y": .5}
|
||||
"""
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.screen = Builder.load_string(KV)
|
||||
|
||||
def build(self):
|
||||
return self.screen
|
||||
|
||||
|
||||
Test().run()
|
||||
+19
-15
@@ -17,17 +17,13 @@ from queue import Queue
|
||||
from kivy.app import App
|
||||
from kivy.core.clipboard import Clipboard
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import ObjectProperty
|
||||
from kivy.resources import resource_add_path
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.screenmanager import Screen
|
||||
from kivymd.app import MDApp
|
||||
from katrain.core.ai import ai_move
|
||||
from katrain.core.utils import (
|
||||
DEFAULT_LANGUAGE,
|
||||
find_package_resource,
|
||||
i18n,
|
||||
)
|
||||
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_KATAGO_STDERR, OUTPUT_INFO, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG, MODE_PLAY
|
||||
from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup
|
||||
@@ -36,10 +32,10 @@ from katrain.core.base_katrain import KaTrainBase
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.game import Game, IllegalMoveException, KaTrainSGF
|
||||
from katrain.core.sgf_parser import Move, ParseError
|
||||
from katrain.gui.kivyutils import *
|
||||
from katrain.gui.widgets.graph import ScoreGraph
|
||||
from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget
|
||||
from katrain.gui.controlspanel import ControlsPanel
|
||||
from katrain.gui.graph import ScoreGraph
|
||||
from katrain.gui.kivyutils import *
|
||||
from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup
|
||||
from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL
|
||||
|
||||
@@ -240,6 +236,13 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
self.config_popup.content.popup = self.config_popup
|
||||
self.config_popup.open()
|
||||
|
||||
def _do_ai_popup(self):
|
||||
self.controls.timer.paused = True
|
||||
if not self.ai_settings_popup:
|
||||
self.ai_settings_popup = I18NPopup(title_key="ai settings", size=[800, 800], content=ConfigAIPopupContents(self, self.ai_settings_popup)).__self__
|
||||
self.ai_settings_popup.content.popup = self.ai_settings_popup
|
||||
self.ai_settings_popup.open()
|
||||
|
||||
# todo pop
|
||||
|
||||
def _do_analyze_sgf_popup(self):
|
||||
@@ -263,13 +266,6 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
popup_contents.filesel.on_submit = readfile
|
||||
self.fileselect_popup.open()
|
||||
|
||||
def _do_ai_popup(self):
|
||||
self.controls.timer.paused = True
|
||||
if not self.ai_settings_popup: # persist state of popup etc
|
||||
self.ai_settings_popup = Popup(title="Edit AI Settings", size_hint=(0.7, 0.8)).__self__
|
||||
self.ai_settings_popup.add_widget(ConfigAIPopupContents(self, self.ai_settings_popup))
|
||||
self.ai_settings_popup.open()
|
||||
|
||||
def _do_output_sgf(self):
|
||||
for pl in Move.PLAYERS:
|
||||
if not self.game.root.get_property(f"P{pl}"):
|
||||
@@ -326,8 +322,14 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
}
|
||||
|
||||
def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
|
||||
if isinstance(App.get_running_app().root_window.children[0], Popup) or self.controls.note.focus:
|
||||
return # if in new game or load, don't allow keyboard shortcuts
|
||||
if self.controls.note.focus:
|
||||
return # when making notes, don't allow keyboard shortcuts
|
||||
first_child = App.get_running_app().root_window.children[0]
|
||||
if isinstance(first_child, Popup):
|
||||
if keycode[1] not in ["f5", "f6", "f7", "f8"]:
|
||||
return
|
||||
first_child.dismiss()
|
||||
|
||||
shortcuts = self.shortcuts
|
||||
if keycode[1] in shortcuts.keys():
|
||||
shortcut = shortcuts[keycode[1]]
|
||||
@@ -337,6 +339,8 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
self(*shortcut)
|
||||
elif keycode[1] == "tab":
|
||||
self.play_mode.switch_mode()
|
||||
elif keycode[1] == "shift":
|
||||
self.nav_drawer.set_state("toggle")
|
||||
elif keycode[1] == "spacebar":
|
||||
self.controls.timer.paused = not self.controls.timer.paused
|
||||
elif keycode[1] in ["`", "~", "m"]:
|
||||
|
||||
+5
-5
@@ -5,10 +5,10 @@
|
||||
"model": "katrain/models/g170e-b15c192-s1672170752-d466197061.bin.gz",
|
||||
"config": "katrain/KataGo/analysis_config.cfg",
|
||||
"threads": 16,
|
||||
"max_visits": 500,
|
||||
"max_visits": 750,
|
||||
"fast_visits": 50,
|
||||
"max_time": 3.0,
|
||||
"wide_root_noise": 0.0,
|
||||
"wide_root_noise": 0.01,
|
||||
"_hint_wide_root_noise": "A higher value here (typically 0.05-0.1)\nmakes the analysis explore more moves\nat the cost of some strength.",
|
||||
"_enable_ownership": true
|
||||
},
|
||||
@@ -52,8 +52,8 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
false,
|
||||
false
|
||||
],
|
||||
"show_dots": [
|
||||
true,
|
||||
@@ -65,7 +65,7 @@
|
||||
],
|
||||
"eval_off_show_last": 3,
|
||||
"eval_show_ai": true,
|
||||
"lock_ai": false
|
||||
"lock_ai": true
|
||||
},
|
||||
"ai": {
|
||||
"ai:default": {
|
||||
|
||||
+16
-8
@@ -5,7 +5,6 @@
|
||||
#:set TOOLTIP_COLOR [0.43, 0.50, 0.65, 1]
|
||||
#:set ICON_BUTTON_COLOR [0.71, 0.78, 0.81, 1]
|
||||
#:set ICON_BUTTON_COLOR_DOWN [0.51, 0.58, 0.51, 1]
|
||||
#:set GRAPH_DOT_COLOR [0.85, 0.3, 0.3,1]
|
||||
|
||||
#-- NEW
|
||||
|
||||
@@ -35,6 +34,8 @@
|
||||
#:set SCORE_COLOR LIGHTBLUE
|
||||
#:set POINTLOSS_COLOR YELLOW
|
||||
#:set WINRATE_COLOR GREEN
|
||||
#:set GRAPH_DOT_COLOR [0.85, 0.3, 0.3,1]
|
||||
|
||||
|
||||
#:set BUTTON_INACTIVE_COLOR LIGHTGREY
|
||||
|
||||
@@ -469,15 +470,15 @@
|
||||
Line:
|
||||
points: [root.pos[0],root.whalf,root.pos[0]+root.width,root.whalf]
|
||||
width: 1
|
||||
# dashes: [2,2]
|
||||
dashes: [2,2]
|
||||
Line:
|
||||
points: [root.pos[0],root.mid,root.pos[0]+root.width,root.mid]
|
||||
width: 1
|
||||
# dashes: [4,4]
|
||||
dashes: [4,4]
|
||||
Line:
|
||||
points: [root.pos[0],root.bhalf,root.pos[0]+root.width,root.bhalf]
|
||||
width: 1
|
||||
# dashes: [2,2]
|
||||
dashes: [2,2]
|
||||
canvas.after:
|
||||
Color:
|
||||
rgba: SCORE_COLOR
|
||||
@@ -489,13 +490,18 @@
|
||||
Line:
|
||||
points: root.winrate_points if root.show_winrate else []
|
||||
width: 1.1
|
||||
|
||||
Color:
|
||||
rgba: GRAPH_DOT_COLOR
|
||||
Ellipse:
|
||||
id: dot
|
||||
pos: root.dot_pos
|
||||
size: self.highlight_size,self.highlight_size
|
||||
id: score_dot
|
||||
pos: root.score_dot_pos
|
||||
size: (self.highlight_size,self.highlight_size) if root.show_score else (0.0001,0.0001)
|
||||
Color:
|
||||
rgba: GRAPH_DOT_COLOR
|
||||
Ellipse:
|
||||
id: winrate_dot
|
||||
pos: root.winrate_dot_pos
|
||||
size: (self.highlight_size,self.highlight_size) if root.show_winrate else (0.0001,0.0001)
|
||||
# score ticks
|
||||
GraphMarkerLabel:
|
||||
font_size: root.marker_font_size
|
||||
@@ -1001,9 +1007,11 @@
|
||||
text: i18n._('btn:Play')
|
||||
id: play
|
||||
state: 'down'
|
||||
on_state: app.gui.update_state()
|
||||
PlayAnalyzeButton:
|
||||
text: i18n._('btn:Analysis')
|
||||
id: analyze
|
||||
on_state: app.gui.update_state()
|
||||
|
||||
<KaTrainGui>:
|
||||
board_gui: board_gui
|
||||
|
||||
@@ -222,7 +222,7 @@ class BadukPanWidget(Widget):
|
||||
outline_color = OUTLINE_COLORS
|
||||
katrain = self.katrain
|
||||
board_size_x, board_size_y = katrain.game.board_size
|
||||
lock_ai = self.trainer_config.get("lock_ai", False) and katrain.controls.teaching_mode_enabled()
|
||||
lock_ai = self.trainer_config["lock_ai"] and self.katrain.play_analyze_mode == MODE_PLAY
|
||||
show_n_eval = self.trainer_config["eval_off_show_last"]
|
||||
|
||||
self.canvas.clear()
|
||||
@@ -328,7 +328,7 @@ class BadukPanWidget(Widget):
|
||||
current_node = katrain.game.current_node
|
||||
player, next_player = current_node.player, current_node.next_player
|
||||
stone_color = STONE_COLORS
|
||||
lock_ai = self.trainer_config.get("lock_ai", False) and self.katrain.controls.play_analyze_mode == MODE_PLAY
|
||||
lock_ai = self.trainer_config["lock_ai"] and self.katrain.play_analyze_mode == MODE_PLAY
|
||||
|
||||
self.canvas.after.clear()
|
||||
with self.canvas.after:
|
||||
|
||||
+11
-3
@@ -33,8 +33,15 @@ class LabelledTextInput(MDTextField):
|
||||
|
||||
|
||||
class LabelledPathInput(LabelledTextInput):
|
||||
def on_text(self, widget, text, **kwargs):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Clock.schedule_once(self.check_error, 0)
|
||||
|
||||
def check_error(self, _dt=None):
|
||||
self.error = not os.path.exists(find_package_resource(self.input_value))
|
||||
|
||||
def on_text(self, widget, text, **kwargs):
|
||||
self.check_error()
|
||||
return super().on_text(widget, text, **kwargs)
|
||||
|
||||
@property
|
||||
@@ -130,8 +137,9 @@ class QuickConfigGui(MDBoxLayout):
|
||||
|
||||
if "::" in keys[-1]:
|
||||
arraykey, ix = keys[-1].split("::")
|
||||
ix = int(ix)
|
||||
array = config[arraykey]
|
||||
return array[int(ix)], array, ix
|
||||
return array[ix], array, ix
|
||||
else:
|
||||
if keys[-1] not in config:
|
||||
config[keys[-1]] = ""
|
||||
@@ -249,7 +257,7 @@ class ConfigPopup(QuickConfigGui):
|
||||
old_proc = old_engine.katago_process
|
||||
if old_proc:
|
||||
old_engine.shutdown(finish=False)
|
||||
new_engine = KataGoEngine(self.katrain, self.config["engine"])
|
||||
new_engine = KataGoEngine(self.katrain, self.katrain.config("engine"))
|
||||
self.katrain.engine = new_engine
|
||||
self.katrain.game.engines = {"B": new_engine, "W": new_engine}
|
||||
self.katrain.game.analyze_all_nodes() # old engine was possibly broken, so make sure we redo any failures
|
||||
|
||||
Whitespace-only changes.
@@ -13,9 +13,10 @@ class ScoreGraph(BackgroundMixin):
|
||||
score_points = ListProperty([])
|
||||
winrate_points = ListProperty([])
|
||||
|
||||
dot_pos = ListProperty([0, 0])
|
||||
score_dot_pos = ListProperty([0, 0])
|
||||
winrate_dot_pos = ListProperty([0, 0])
|
||||
highlighted_index = NumericProperty(None)
|
||||
highlight_size = NumericProperty(5)
|
||||
highlight_size = NumericProperty(6)
|
||||
|
||||
score_scale = NumericProperty(5)
|
||||
winrate_scale = NumericProperty(5)
|
||||
@@ -63,10 +64,14 @@ class ScoreGraph(BackgroundMixin):
|
||||
|
||||
if self.highlighted_index is not None:
|
||||
self.highlighted_index = min(self.highlighted_index, len(score_values) - 1)
|
||||
dot_point = score_line_points[self.highlighted_index]
|
||||
if math.isnan(dot_point[1]):
|
||||
dot_point[1] = self.pos[1] + self.height / 2 + available_height / 2 * ((score_nn_values or [0])[-1] / self.score_scale)
|
||||
self.dot_pos = [c - self.highlight_size / 2 for c in dot_point]
|
||||
score_dot_point = score_line_points[self.highlighted_index]
|
||||
winrate_dot_point = winrate_line_points[self.highlighted_index]
|
||||
if math.isnan(score_dot_point[1]):
|
||||
score_dot_point[1] = self.pos[1] + self.height / 2 + available_height / 2 * ((score_nn_values or [0])[-1] / self.score_scale)
|
||||
self.score_dot_pos = [c - self.highlight_size / 2 for c in winrate_dot_point]
|
||||
if math.isnan(winrate_dot_point[1]):
|
||||
winrate_dot_point[1] = self.pos[1] + self.height / 2 + available_height / 2 * ((winrate_nn_values or [0])[-1] / self.winrate_scale)
|
||||
self.winrate_dot_pos = [c - self.highlight_size / 2 for c in score_dot_point]
|
||||
|
||||
def update_value(self, node):
|
||||
self.highlighted_index = index = node.depth
|
||||
Binary file not shown.
@@ -269,10 +269,7 @@ msgstr ""
|
||||
|
||||
#. hints on new game
|
||||
msgid "non square board hint"
|
||||
msgstr "Use x:y (e.g. 19:9) to play on a non-square board."
|
||||
|
||||
msgid "handicap hint"
|
||||
msgstr "Note that handicaps above 9 are not supported on non-square boards."
|
||||
msgstr "Use x:y (e.g. 19:9) for non-square size"
|
||||
|
||||
#. submit new game
|
||||
msgid "new game"
|
||||
@@ -310,10 +307,10 @@ msgstr ""
|
||||
"Show dots/SGF comments\n"
|
||||
" for AI players"
|
||||
|
||||
msgid "lock ai when teaching"
|
||||
msgid "lock ai when playing"
|
||||
msgstr ""
|
||||
"Disable analysis\n"
|
||||
" while in teach mode"
|
||||
" while in play mode"
|
||||
|
||||
#. submit button
|
||||
msgid "update teacher"
|
||||
@@ -360,6 +357,14 @@ msgstr "Maximum number of visits in fast analysis"
|
||||
msgid "engine:max_time"
|
||||
msgstr "Maximum time for analysis"
|
||||
|
||||
msgid "engine:wide_root_noise"
|
||||
msgstr "Wide root noise (increases variety of moves considered)"
|
||||
|
||||
msgid "engine:wide_root_noise:hint"
|
||||
msgstr ""
|
||||
"Use 0.02-0.1 to show\n"
|
||||
"more possible moves"
|
||||
|
||||
msgid "engine:time:hint"
|
||||
msgstr "Time in seconds"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -602,3 +602,19 @@ msgstr "Leave blank to use included executable"
|
||||
#. TODO
|
||||
msgid "file not found"
|
||||
msgstr "Path does not exist"
|
||||
|
||||
#. TODO
|
||||
msgid "engine:wide_root_noise:hint"
|
||||
msgstr ""
|
||||
"Use a value of 0.02-0.1 to show more possible moves, at the cost of "
|
||||
"weakening analysis."
|
||||
|
||||
#. TODO
|
||||
msgid "engine:wide_root_noise"
|
||||
msgstr "Wide root noise (increases variety of moves considered)"
|
||||
|
||||
#. TODO
|
||||
msgid "lock ai when playing"
|
||||
msgstr ""
|
||||
"Disable analysis\n"
|
||||
" while in play mode"
|
||||
Binary file not shown.
@@ -634,3 +634,19 @@ msgstr "Leave blank to use included executable"
|
||||
#. TODO
|
||||
msgid "file not found"
|
||||
msgstr "Path does not exist"
|
||||
|
||||
#. TODO
|
||||
msgid "engine:wide_root_noise:hint"
|
||||
msgstr ""
|
||||
"Use a value of 0.02-0.1 to show more possible moves, at the cost of "
|
||||
"weakening analysis."
|
||||
|
||||
#. TODO
|
||||
msgid "engine:wide_root_noise"
|
||||
msgstr "Wide root noise (increases variety of moves considered)"
|
||||
|
||||
#. TODO
|
||||
msgid "lock ai when playing"
|
||||
msgstr ""
|
||||
"Disable analysis\n"
|
||||
" while in play mode"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
+11
-25
@@ -30,6 +30,7 @@
|
||||
font_size: dp(16)
|
||||
multiline: True # just for wrapping
|
||||
helper_text_mode: "on_error"
|
||||
helper_text: i18n._("file not found")
|
||||
|
||||
|
||||
<LabelledIntInput>:
|
||||
@@ -79,27 +80,22 @@
|
||||
orientation: 'horizontal'
|
||||
GridLayout:
|
||||
cols: 2
|
||||
rows: 9
|
||||
rows: 8
|
||||
spacing: CP_SPACING
|
||||
Label:
|
||||
Label:
|
||||
DescriptionLabel:
|
||||
text: i18n._('General Settings')
|
||||
font_size: DESC_FONT_SIZE * 1.5
|
||||
DescriptionLabel:
|
||||
text: i18n._("general:sgf_load")
|
||||
multiline: True
|
||||
font_size: DESC_FONT_SIZE * 0.8
|
||||
AnchorLayout:
|
||||
LabelledPathInput:
|
||||
input_property: "general/sgf_load" # just last load dir?
|
||||
multiline: True
|
||||
font_size: DESC_FONT_SIZE * 0.8
|
||||
DescriptionLabel:
|
||||
text: i18n._("general:sgf_save")
|
||||
AnchorLayout:
|
||||
LabelledPathInput:
|
||||
input_property: "general/sgf_save"
|
||||
multiline: True
|
||||
font_size: DESC_FONT_SIZE * 0.8
|
||||
DescriptionLabel:
|
||||
text: i18n._("general:anim_pv_time")
|
||||
AnchorLayout:
|
||||
@@ -118,18 +114,17 @@
|
||||
Label:
|
||||
Label:
|
||||
Label:
|
||||
Label:
|
||||
Label:
|
||||
BackgroundMixin:
|
||||
background_color: LIGHTGREY
|
||||
size_hint: None, 1
|
||||
width: 2
|
||||
GridLayout:
|
||||
cols: 2
|
||||
rows: 9
|
||||
rows: 8
|
||||
spacing: CP_SPACING
|
||||
Label:
|
||||
Label:
|
||||
DescriptionLabel:
|
||||
font_size: DESC_FONT_SIZE * 1.5
|
||||
text: i18n._('Engine Settings')
|
||||
DescriptionLabel:
|
||||
text: i18n._("engine:katago")
|
||||
@@ -137,7 +132,6 @@
|
||||
LabelledPathInput:
|
||||
input_property: "engine/katago"
|
||||
hint_text: i18n._("engine:katago:hint")
|
||||
helper_text: i18n._("file not found")
|
||||
DescriptionLabel:
|
||||
text: i18n._("engine:model")
|
||||
AnchorLayout:
|
||||
@@ -164,17 +158,12 @@
|
||||
LabelledFloatInput:
|
||||
input_property: "engine/max_time"
|
||||
helper_text: i18n._("engine:time:hint")
|
||||
helper_text_mode: "on_focus"
|
||||
DescriptionLabel:
|
||||
text: i18n._("engine:threads")
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
input_property: "engine/threads"
|
||||
DescriptionLabel:
|
||||
text: i18n._("engine:wide_root_noise")
|
||||
AnchorLayout:
|
||||
LabelledFloatInput:
|
||||
input_property: "engine/wide_root_noise"
|
||||
hint_text: i18n._("engine:wide_root_noise:hint")
|
||||
AnchorLayout:
|
||||
size_hint: 1,1.5
|
||||
AutoSizedRoundedRectangleButton:
|
||||
@@ -222,7 +211,7 @@
|
||||
input_property: "trainer/eval_show_ai"
|
||||
DescriptionLabel:
|
||||
size_hint: 2,1
|
||||
text: i18n._("lock ai when teaching")
|
||||
text: i18n._("lock ai when playing")
|
||||
AnchorLayout:
|
||||
LabelledCheckBox:
|
||||
input_property: "trainer/lock_ai"
|
||||
@@ -272,7 +261,7 @@
|
||||
GridLayout:
|
||||
rows: 6
|
||||
cols: 3
|
||||
size_hint: 1, 3
|
||||
size_hint: 1, 2.5
|
||||
spacing: CP_SPACING
|
||||
padding: CP_PADDING
|
||||
DescriptionLabel:
|
||||
@@ -283,6 +272,7 @@
|
||||
id: boardsize
|
||||
input_property: 'game/size'
|
||||
size_hint: 0.5, None
|
||||
hint_text:i18n._("non square board hint")
|
||||
AnchorLayout:
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
@@ -353,10 +343,6 @@
|
||||
input_property: 'game/clear_cache'
|
||||
SmallDescriptionLabel:
|
||||
text: i18n._('avoids replaying')
|
||||
SmallDescriptionLabel:
|
||||
text: i18n._("non square board hint")
|
||||
SmallDescriptionLabel:
|
||||
text: i18n._("handicap hint")
|
||||
AnchorLayout:
|
||||
size_hint: 1,1
|
||||
AutoSizedRoundedRectangleButton:
|
||||
|
||||
Reference in new issue
Block a user