wtf buttons too wide
This commit is contained in:
1 parent
8fc7952519
commit
a136ff6407
9 files changed
+532
-248
No files matched your search
+2
-2
@@ -2,9 +2,9 @@ Primary author and project maintainer (https://github.com/sanderland/katrain):
|
||||
Sander Land
|
||||
|
||||
Many thanks to these additional authors:
|
||||
"Kameone" - for MacOS installation instructions and suggestions for improvements.
|
||||
"Kameone" - for design of the v1.1 UI, MacOS installation instructions, and suggestions for improvements.
|
||||
|
||||
Additional thanks to:
|
||||
"Dontbtme" for an incredible amount of detailed feedback and early testing of v1.0.
|
||||
"Dontbtme" for detailed feedback and early testing of v1.0.
|
||||
|
||||
|
||||
@@ -24,6 +24,16 @@ but has since grown to include a wide range of features, including:
|
||||
* To play against AI, pick an AI from the dropdown and either 'human' or 'teach' for yourself and start playing.
|
||||
* For different board sizes, use the button with the little goban in the bottom right for a new game.
|
||||
|
||||
|
||||
# Documentation
|
||||
[](#Installation)
|
||||
[](https://pypi.org/project/katrain)
|
||||
[](https://pepy.tech/project/katrain)
|
||||

|
||||

|
||||
[](https://github.com/psf/black)
|
||||
[](https://discord.com/channels/417022162348802048/629446365688365067)
|
||||
|
||||
## Installation
|
||||
* See the [releases tab](https://github.com/sanderland/katrain/releases) for pre-built installers for windows.
|
||||
* Alternatively use `pip3 install -U katrain` to install the latest version from PyPI on any OS.
|
||||
|
||||
+6
-8
@@ -26,7 +26,7 @@ from katrain.core.game import Game, IllegalMoveException, KaTrainSGF
|
||||
from katrain.core.sgf_parser import Move, ParseError
|
||||
from katrain.gui.kivyutils import *
|
||||
from katrain.gui.badukpan import BadukPanWidget
|
||||
from katrain.gui.controls import Controls
|
||||
from katrain.gui.controlspanel import ControlsPanel
|
||||
from katrain.gui.popups import NewGamePopup, ConfigPopup, LoadSGFPopup
|
||||
|
||||
__version__ = "1.0.6"
|
||||
@@ -47,7 +47,6 @@ class KaTrainGui(BoxLayout):
|
||||
self.config_file = self._load_config()
|
||||
|
||||
self.debug_level = self.config("debug/level", OUTPUT_INFO)
|
||||
self.controls.ai_mode_groups["W"].values = self.controls.ai_mode_groups["B"].values = list(self.config("ai").keys())
|
||||
self.message_queue = Queue()
|
||||
|
||||
self._keyboard = Window.request_keyboard(None, self, "")
|
||||
@@ -174,7 +173,6 @@ class KaTrainGui(BoxLayout):
|
||||
self.board_gui.animating_pv = None
|
||||
self.engine.on_new_game() # clear queries
|
||||
self.game = Game(self, self.engine, self.config("game"), move_tree=move_tree, analyze_fast=analyze_fast)
|
||||
self.controls.select_mode("analyze" if move_tree and len(move_tree.nodes_in_tree) > 1 else "play")
|
||||
self.controls.graph.initialize_from_game(self.game.root)
|
||||
self.controls.periods_used = {"B": 0, "W": 0}
|
||||
self.update_state(redraw_board=True)
|
||||
@@ -287,11 +285,11 @@ class KaTrainGui(BoxLayout):
|
||||
return # if in new game or load, don't allow keyboard shortcuts
|
||||
|
||||
shortcuts = {
|
||||
"q": self.controls.show_children,
|
||||
"w": self.controls.eval,
|
||||
"e": self.controls.hints,
|
||||
"r": self.controls.ownership,
|
||||
"t": self.controls.policy,
|
||||
"q": self.analysis_controls.show_children,
|
||||
"w": self.analysis_controls.eval,
|
||||
"e": self.analysis_controls.hints,
|
||||
"r": self.analysis_controls.ownership,
|
||||
"t": self.analysis_controls.policy,
|
||||
"enter": ("ai-move",),
|
||||
"a": self.controls.analyze_extra,
|
||||
"s": self.controls.analyze_equalize,
|
||||
|
||||
@@ -12,6 +12,10 @@ OUTPUT_INFO = 0
|
||||
OUTPUT_DEBUG = 1
|
||||
OUTPUT_EXTRA_DEBUG = 2
|
||||
|
||||
PLAYER_HUMAN = 0
|
||||
PLAYER_HUMAN_TEACHING = 1
|
||||
PLAYER_AI = 2
|
||||
|
||||
|
||||
def var_to_grid(array_var: List[Any], size: Tuple[int, int]) -> List[List[Any]]:
|
||||
"""convert ownership/policy to grid format such that grid[y][x] is for move with coords x,y"""
|
||||
|
||||
+470
-178
@@ -13,10 +13,14 @@
|
||||
#:set WHITE [0.95,0.95,0.95,1]
|
||||
#:set GREY [0.5,0.5,0.5,1]
|
||||
|
||||
#:set BACKGROUND_COLOR [0,0,1,1]
|
||||
#-- NEW
|
||||
|
||||
#:set BACKGROUND_COLOR [36/255,48/255,62/255,1]
|
||||
#:set BOX_BACKGROUND_COLOR [46/255,65/255,88/255,1]
|
||||
#:set BOX_BACKGROUND_COLOR [56/255,75/255,98/255,1]
|
||||
|
||||
|
||||
# mixins
|
||||
|
||||
<BackgroundColor>:
|
||||
canvas.before:
|
||||
Color:
|
||||
@@ -145,7 +149,7 @@
|
||||
valign: 'center'
|
||||
|
||||
<LargeLabel@DarkLabel>:
|
||||
bold: True
|
||||
bold: True
|
||||
|
||||
<TimerLabel@Label>
|
||||
color: BLACK
|
||||
@@ -154,7 +158,7 @@
|
||||
valign: 'middle'
|
||||
markup: True
|
||||
multiline: False
|
||||
font_name: 'img/fonts/digital-7 (mono).ttf'
|
||||
font_name: 'img/fonts/digital-7.ttf'
|
||||
|
||||
<ScaledLightLabel>: # popups
|
||||
color: WHITE
|
||||
@@ -179,31 +183,31 @@
|
||||
<CheckBoxHintLabel@Label+ToolTipBehavior>:
|
||||
font_size: 0.4*self.size[1]
|
||||
color: BLACK
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
size: self.texture_size
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
size: self.texture_size
|
||||
|
||||
<ClickableLabel@ButtonBehavior+DarkLabel>:
|
||||
color: BLACK
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
size: self.texture_size
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
size: self.texture_size
|
||||
|
||||
<CensorableLabel>:
|
||||
value : value
|
||||
orientation: 'horizontal'
|
||||
orientation: 'horizontal'
|
||||
ClickableLabel:
|
||||
size_hint: 0.6,1
|
||||
id: desc
|
||||
text: root.label
|
||||
font_size: self.size[1] * 0.8
|
||||
on_press: value.opacity = 1 if value.opacity==0 else 0
|
||||
DarkLabel:
|
||||
size_hint: 0.4,1
|
||||
text: root.text
|
||||
font_size: desc.font_size
|
||||
id: value
|
||||
bold: True
|
||||
size_hint: 0.6,1
|
||||
id: desc
|
||||
text: root.label
|
||||
font_size: self.size[1] * 0.8
|
||||
on_press: value.opacity = 1 if value.opacity==0 else 0
|
||||
DarkLabel:
|
||||
size_hint: 0.4,1
|
||||
text: root.text
|
||||
font_size: desc.font_size
|
||||
id: value
|
||||
bold: True
|
||||
|
||||
<BaseCircleWithText>:
|
||||
text: ''
|
||||
@@ -244,59 +248,13 @@
|
||||
color: BUTTON_COLOR
|
||||
size: self.texture_size
|
||||
|
||||
<ScoreGraph>:
|
||||
whalf: root.pos[1]+self.height*0.25*(1 + 2 * root.marginy)
|
||||
bhalf: root.pos[1]+self.height*0.75*(1 - 2/3 * root.marginy)
|
||||
mid: root.pos[1]+self.height*0.5
|
||||
right_edge: root.pos[0] + root.width * (1 - root.marginx)
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: GREY
|
||||
Line:
|
||||
points: [root.pos[0]+self.width*root.marginx,root.whalf,root.pos[0]+self.width*(1-root.marginx),root.whalf]
|
||||
width: 1.0
|
||||
dashes: [1,2]
|
||||
Line:
|
||||
points: [root.pos[0]+self.width*root.marginx,root.mid,root.pos[0]+self.width*(1-root.marginx),root.mid]
|
||||
width: 1.0
|
||||
dashes: [4,4]
|
||||
Line:
|
||||
points: [root.pos[0]+self.width*root.marginx,root.bhalf,root.pos[0]+self.width*(1-root.marginx),root.bhalf]
|
||||
width: 1.0
|
||||
dashes: [1,2]
|
||||
Line:
|
||||
rectangle: [root.pos[0] + root.marginx * self.width, root.pos[1] + root.marginy*self.height, self.width*(1-2*root.marginx), root.height*(1-2*root.marginy)]
|
||||
canvas.after:
|
||||
Color:
|
||||
rgba: BLACK
|
||||
Line:
|
||||
points: root.line_points
|
||||
width: 1.1
|
||||
Color:
|
||||
rgba: GRAPH_DOT_COLOR
|
||||
Ellipse:
|
||||
id: dot
|
||||
pos: root.dot_pos
|
||||
size: self.highlight_size,self.highlight_size
|
||||
GraphMarkerLabel:
|
||||
font_size: 0.1 * root.height
|
||||
id: range_label_top
|
||||
pos: root.right_edge - self.width-1, root.pos[1]+root.height*(1-root.marginy) - self.font_size
|
||||
text: 'B+' + str(int(root.y_scale))
|
||||
GraphMarkerLabel:
|
||||
font_size: 0.1 * root.height
|
||||
pos: root.right_edge - self.width-1, root.mid - self.height/2 + 2
|
||||
text: 'Jigo'
|
||||
GraphMarkerLabel:
|
||||
font_size: 0.1 * root.height
|
||||
pos: root.right_edge - self.width-1, root.pos[1]
|
||||
text: 'W+' + str(int(root.y_scale))
|
||||
|
||||
|
||||
# UI components
|
||||
|
||||
<BadukPanWidget>:
|
||||
size: self.parent.height, self.parent.height
|
||||
katrain: app.gui
|
||||
size: self.parent.height, self.parent.height
|
||||
katrain: app.gui
|
||||
|
||||
<BadukPanControls>:
|
||||
katrain: app.gui
|
||||
@@ -387,6 +345,188 @@
|
||||
Label:
|
||||
size_hint: 0.025, 1
|
||||
|
||||
<AnalysisControls@BoxLayout>:
|
||||
katrain: app.gui
|
||||
show_children: show_children
|
||||
eval: eval
|
||||
policy: policy
|
||||
hints: hints
|
||||
ownership: ownership
|
||||
Label:
|
||||
text: '='
|
||||
size_hint_x: None
|
||||
width: self.height
|
||||
GridLayout:
|
||||
cols: 5
|
||||
rows: 2
|
||||
size_hint: 0.5, 1
|
||||
CheckBoxHintLabel:
|
||||
text: 'next\nmoves'
|
||||
tooltip_text: '[q]\nShow all next moves\nplayed from the current position'
|
||||
CheckBoxHintLabel:
|
||||
text: 'all\ndots'
|
||||
tooltip_text: '[w]\nShow evaluation dots\non stones for all moves'
|
||||
CheckBoxHintLabel:
|
||||
text: 'top\nmoves'
|
||||
tooltip_text: '[e]\nShow top\npredicted moves'
|
||||
CheckBoxHintLabel:
|
||||
text: 'show\nowner'
|
||||
tooltip_text: '[r]\nShow predicted ownership\nof each intersection'
|
||||
CheckBoxHintLabel:
|
||||
text: 'NN\npolicy'
|
||||
tooltip_text: "[t]\nShow Policy top moves\n(i.e. with no 'reading')"
|
||||
CheckBox:
|
||||
id: show_children
|
||||
active: True
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBox:
|
||||
id: eval
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBox:
|
||||
id: hints
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBox:
|
||||
id: ownership
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBox:
|
||||
id: policy
|
||||
on_active: root.katrain.update_state()
|
||||
BoxLayout:
|
||||
spacing:1
|
||||
padding: 1,2,1,2
|
||||
size_hint: 0.5, 1
|
||||
StyledButton:
|
||||
id: analyze_extra
|
||||
text: 'Extra'
|
||||
tooltip_text: '[a]\nExtra analysis\nto increase accuracy\nof top move predictions\nand score difference'
|
||||
size_hint: 1, 1
|
||||
on_press: if not self.last_touch or self.last_touch.button=="left": root.katrain("analyze-extra","extra")
|
||||
StyledButton:
|
||||
id: analyze_equalize
|
||||
text: 'Equalize'
|
||||
tooltip_text: '[s]\nAnalysis of all\npredicted top moves\nwith the same accuracy'
|
||||
size_hint: 1, 1
|
||||
on_press: if not self.last_touch or self.last_touch.button=="left": root.katrain("analyze-extra","equalize")
|
||||
StyledButton:
|
||||
id: analyze_sweep
|
||||
text: 'Sweep'
|
||||
tooltip_text: '[d]\nFast analysis\nof all legal moves'
|
||||
size_hint: 1, 1
|
||||
on_press: if not self.last_touch or self.last_touch.button=="left": root.katrain("analyze-extra","sweep")
|
||||
|
||||
# ---------- components of right pane -------
|
||||
|
||||
<ControlPanelSection@BoxLayout+BackgroundColor>:
|
||||
background: BOX_BACKGROUND_COLOR
|
||||
|
||||
<PlayerInfo@BoxLayout>:
|
||||
BlackCircleWithText:
|
||||
text: '0'
|
||||
size_hint: 0.2, 1
|
||||
id: captures
|
||||
Label:
|
||||
id: description
|
||||
|
||||
<PonderInfo@ControlPanelSection>:
|
||||
Label:
|
||||
text: 'AI\nPonder\nO'
|
||||
|
||||
<Timer@ControlPanelSection>:
|
||||
state: (30,5,1)
|
||||
paused: True
|
||||
TimerLabel:
|
||||
size_hint: 0.45,1
|
||||
id: timer
|
||||
text: f"{root.state[0]//60}:{int(root.state[0] % 60):02d}"
|
||||
DarkLabel:
|
||||
size_hint: 0.125,1
|
||||
id: periods
|
||||
markup: True
|
||||
text: f"x{root.state[1]}"
|
||||
font_size: self.width*0.5
|
||||
multiline: False
|
||||
valign: 'top'
|
||||
halign: 'left'
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
size_hint: 0.15,1
|
||||
Label:
|
||||
StyledIconStyledToggleButton:
|
||||
id: pause
|
||||
height: self.width
|
||||
size_hint: 1, None
|
||||
icon_margin: 0.2 * self.size[0]
|
||||
icon: 'img/flaticon/pause.png'
|
||||
state: 'down' if root.paused else 'up'
|
||||
on_press: root.paused = not root.paused
|
||||
Label:
|
||||
|
||||
<ScoreGraph>:
|
||||
background: BOX_BACKGROUND_COLOR
|
||||
whalf: root.pos[1]+self.height*0.25*(1 + 2 * root.marginy)
|
||||
bhalf: root.pos[1]+self.height*0.75*(1 - 2/3 * root.marginy)
|
||||
mid: root.pos[1]+self.height*0.5
|
||||
right_edge: root.pos[0] + root.width * (1 - root.marginx)
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: GREY
|
||||
Line:
|
||||
points: [root.pos[0]+self.width*root.marginx,root.whalf,root.pos[0]+self.width*(1-root.marginx),root.whalf]
|
||||
width: 1.0
|
||||
dashes: [1,2]
|
||||
Line:
|
||||
points: [root.pos[0]+self.width*root.marginx,root.mid,root.pos[0]+self.width*(1-root.marginx),root.mid]
|
||||
width: 1.0
|
||||
dashes: [4,4]
|
||||
Line:
|
||||
points: [root.pos[0]+self.width*root.marginx,root.bhalf,root.pos[0]+self.width*(1-root.marginx),root.bhalf]
|
||||
width: 1.0
|
||||
dashes: [1,2]
|
||||
Line:
|
||||
rectangle: [root.pos[0] + root.marginx * self.width, root.pos[1] + root.marginy*self.height, self.width*(1-2*root.marginx), root.height*(1-2*root.marginy)]
|
||||
canvas.after:
|
||||
Color:
|
||||
rgba: BLACK
|
||||
Line:
|
||||
points: root.line_points
|
||||
width: 1.1
|
||||
Color:
|
||||
rgba: GRAPH_DOT_COLOR
|
||||
Ellipse:
|
||||
id: dot
|
||||
pos: root.dot_pos
|
||||
size: self.highlight_size,self.highlight_size
|
||||
GraphMarkerLabel:
|
||||
font_size: 0.1 * root.height
|
||||
id: range_label_top
|
||||
pos: root.right_edge - self.width-1, root.pos[1]+root.height*(1-root.marginy) - self.font_size
|
||||
text: 'B+' + str(int(root.y_scale))
|
||||
GraphMarkerLabel:
|
||||
font_size: 0.1 * root.height
|
||||
pos: root.right_edge - self.width-1, root.mid - self.height/2 + 2
|
||||
text: 'Jigo'
|
||||
GraphMarkerLabel:
|
||||
font_size: 0.1 * root.height
|
||||
pos: root.right_edge - self.width-1, root.pos[1]
|
||||
text: 'W+' + str(int(root.y_scale))
|
||||
|
||||
<StatsBox@BoxLayout>
|
||||
orientation: 'vertical'
|
||||
|
||||
CensorableLabel:
|
||||
id: win_rate
|
||||
label: 'Win rate'
|
||||
text: ''
|
||||
CensorableLabel:
|
||||
id: score
|
||||
label: 'Score Estimate'
|
||||
text: ''
|
||||
CensorableLabel:
|
||||
id: point_loss
|
||||
label: 'Score Estimate'
|
||||
text: ''
|
||||
|
||||
|
||||
<ScrollableLabel>:
|
||||
do_scroll_x: False
|
||||
scroll_type: ['bars']
|
||||
@@ -417,103 +557,29 @@
|
||||
on_ref_press: root.dispatch('on_ref_press',args[1])
|
||||
|
||||
|
||||
<PlayerInfo>:
|
||||
BlackCircleWithText:
|
||||
text: '0'
|
||||
size_hint: 0.2, 1
|
||||
id: captures
|
||||
Label:
|
||||
id: description
|
||||
|
||||
<PonderInfo@BoxLayout>:
|
||||
Label:
|
||||
text: 'AI\nPonder\nO'
|
||||
|
||||
<Timer@BoxLayout>:
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
size_hint: 0.15,1
|
||||
Label:
|
||||
StyledIconButton:
|
||||
height: self.width
|
||||
size_hint: 1, None
|
||||
icon: 'img/flaticon/cogwheel.png'
|
||||
on_release: if not self.last_touch or self.last_touch.button=="left": root.configure_timer()
|
||||
tooltip_text: "Configure timer"
|
||||
Label:
|
||||
Label:
|
||||
size_hint: 0.025, 1
|
||||
TimerLabel:
|
||||
size_hint: 0.45,1
|
||||
id: timer
|
||||
text: '30:00'
|
||||
DarkLabel:
|
||||
size_hint: 0.125,1
|
||||
id: periods
|
||||
markup: True
|
||||
text: 'x5'
|
||||
font_size: self.width*0.5
|
||||
multiline: False
|
||||
valign: 'top'
|
||||
halign: 'left'
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
size_hint: 0.15,1
|
||||
Label:
|
||||
StyledIconStyledToggleButton:
|
||||
id: pause
|
||||
height: self.width
|
||||
size_hint: 1, None
|
||||
icon_margin: 0.2 * self.size[0]
|
||||
icon: 'img/flaticon/pause.png'
|
||||
state: 'down'
|
||||
Label:
|
||||
|
||||
<StatsBox>
|
||||
CensorableLabel:
|
||||
id: win_rate
|
||||
label: 'Win rate'
|
||||
text: ''
|
||||
CensorableLabel:
|
||||
id: score
|
||||
label: 'Score Estimate'
|
||||
text: ''
|
||||
CensorableLabel:
|
||||
id: point_loss
|
||||
label: 'Score Estimate'
|
||||
text: ''
|
||||
|
||||
#:set PLAYER_BOX_HEIGHT 0.1
|
||||
#:set PLAYER_BOX_HEIGHT 0.15
|
||||
#:set TIMER_BOX_HEIGHT 0.3
|
||||
#:set GRAPH_BOX_HEIGHT 0.33
|
||||
#:set STATS_BOX_HEIGHT 0.27
|
||||
#:set INFO_BOX_HEIGHT 1
|
||||
|
||||
<Controls>:
|
||||
orientation: 'vertical'
|
||||
play_tab_button: play_tab_button
|
||||
analyze_tab_button: analyze_tab_button
|
||||
#:set CP_SPACING 2
|
||||
#:set CP_PADDING 2
|
||||
|
||||
<ControlsPanel>:
|
||||
katrain: app.gui
|
||||
orientation: 'vertical'
|
||||
players: {'B':B_player,'W':W_player}
|
||||
ponder: ponder
|
||||
timer:timer
|
||||
graph: graph
|
||||
stats: stats
|
||||
info: info
|
||||
note: note
|
||||
status_label: status_label
|
||||
win_rate: win_rate
|
||||
score_change: score_change
|
||||
score: score
|
||||
show_children: show_children
|
||||
hints: hints
|
||||
ownership: ownership
|
||||
policy: policy
|
||||
eval: eval
|
||||
analyze_extra: analyze_extra
|
||||
analyze_equalize: analyze_equalize
|
||||
analyze_sweep: analyze_sweep
|
||||
player_mode_groups: {'B':B_player_mode,'W':W_player_mode}
|
||||
ai_mode_groups: {'B':B_AI_mode,'W':W_AI_mode}
|
||||
graph: graph
|
||||
timer: timer
|
||||
periods: periods
|
||||
pause: pause
|
||||
katrain: app.gui
|
||||
status: status
|
||||
padding: CP_PADDING
|
||||
spacing: CP_SPACING
|
||||
|
||||
BoxLayout: # -- Players
|
||||
size_hint_y: PLAYER_BOX_HEIGHT
|
||||
PlayerInfo:
|
||||
@@ -522,67 +588,293 @@
|
||||
id: W_player
|
||||
BoxLayout:
|
||||
size_hint_y: TIMER_BOX_HEIGHT
|
||||
spacing: CP_SPACING
|
||||
PonderInfo:
|
||||
id: ponder
|
||||
size_hint_x: 0.2
|
||||
Timer:
|
||||
id: timer
|
||||
BoxLayout:
|
||||
size_hint_x: 0.7
|
||||
ScoreGraph:
|
||||
size_hint_y: GRAPH_BOX_HEIGHT
|
||||
ScoreGraph:
|
||||
id: graph
|
||||
BoxLayout:
|
||||
size_hint_y: STATS_BOX_HEIGHT
|
||||
StatsBox:
|
||||
id: stats
|
||||
BoxLayout:
|
||||
size_hint_y: INFO_BOX_HEIGHT
|
||||
orientation: 'vertical'
|
||||
ScrollableLabel:
|
||||
id: status
|
||||
size_hint: 1, None
|
||||
height: self.parent.height - status_label.height - note.height - 1
|
||||
height: 50 #min(self.parent.height*0.66,self.label.text_size[1])
|
||||
ScrollableLabel:
|
||||
id: info
|
||||
markup: True
|
||||
on_ref_press: root.katrain.board_gui.show_pv_from_comments(args[1])
|
||||
size_hint: 1, None
|
||||
height: self.parent.height - status_label.height - note.height - 1
|
||||
height: self.parent.height - status.height - note.height - 1
|
||||
TextInput:
|
||||
id: note
|
||||
size_hint: 1, None
|
||||
on_text: root.katrain.set_note(self.text)
|
||||
hint_text: 'Your SGF notes for this position here.'
|
||||
height: min( (len(self._lines)+0.7) * self.line_height, self.parent.height - status_label.height - 2.7*self.line_height)
|
||||
height: min( (len(self._lines)+0.7) * self.line_height, self.parent.height - status.height - 2.7*self.line_height)
|
||||
|
||||
<RHSButton@MDRectangleFlatButton>:
|
||||
height: self.width * 0.3
|
||||
width: self.parent.width
|
||||
canvas.before:
|
||||
Color:
|
||||
rgb: (1,0,0,1)
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: (self.size[0]-2,self.size[1]-5)
|
||||
|
||||
|
||||
<ButtonBox@BoxLayout>
|
||||
orientation: 'vertical'
|
||||
spacing: CP_SPACING
|
||||
|
||||
<RightButtonControls@BoxLayout>:
|
||||
orientation: 'vertical'
|
||||
padding: CP_PADDING,CP_PADDING
|
||||
spacing: CP_SPACING
|
||||
ButtonBox:
|
||||
size_hint: 1,PLAYER_BOX_HEIGHT
|
||||
RHSButton:
|
||||
text: 'PASS'
|
||||
ButtonBox:
|
||||
size_hint_y: TIMER_BOX_HEIGHT
|
||||
RHSButton:
|
||||
text: 'MAN'
|
||||
RHSButton:
|
||||
text: 'BYO'
|
||||
ButtonBox:
|
||||
size_hint_y: GRAPH_BOX_HEIGHT
|
||||
RHSButton:
|
||||
text: 'Win %'
|
||||
RHSButton:
|
||||
text: 'Score'
|
||||
RHSButton:
|
||||
text: 'PtLoss'
|
||||
ButtonBox:
|
||||
size_hint_y: STATS_BOX_HEIGHT
|
||||
RHSButton:
|
||||
text: 'Win %'
|
||||
RHSButton:
|
||||
text: 'Score'
|
||||
RHSButton:
|
||||
text: 'PtLoss'
|
||||
ButtonBox:
|
||||
size_hint_y: INFO_BOX_HEIGHT
|
||||
|
||||
|
||||
|
||||
<OldControls@BoxLayout>
|
||||
orientation: 'horizontal'
|
||||
BoxLayout: # ----------- analyze -----------
|
||||
size_hint: 0,1
|
||||
id: analyze_tab
|
||||
padding: [3,3]
|
||||
opacity: 0
|
||||
orientation: 'vertical'
|
||||
GridLayout:
|
||||
cols: 5
|
||||
rows: 2
|
||||
size_hint: 1, 0.3
|
||||
CheckBoxHintLabel:
|
||||
text: 'next\nmoves'
|
||||
tooltip_text: '[q]\nShow all next moves\nplayed from the current position'
|
||||
CheckBoxHintLabel:
|
||||
text: 'all\ndots'
|
||||
tooltip_text: '[w]\nShow evaluation dots\non stones for all moves'
|
||||
CheckBoxHintLabel:
|
||||
text: 'top\nmoves'
|
||||
tooltip_text: '[e]\nShow top\npredicted moves'
|
||||
CheckBoxHintLabel:
|
||||
text: 'show\nowner'
|
||||
tooltip_text: '[r]\nShow predicted ownership\nof each intersection'
|
||||
CheckBoxHintLabel:
|
||||
text: 'NN\npolicy'
|
||||
tooltip_text: "[t]\nShow Policy top moves\n(i.e. with no 'reading')"
|
||||
CheckBox:
|
||||
id: show_children
|
||||
active: True
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBox:
|
||||
id: eval
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBox:
|
||||
id: hints
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBox:
|
||||
id: ownership
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBox:
|
||||
id: policy
|
||||
on_active: root.katrain.update_state()
|
||||
BoxLayout:
|
||||
spacing:1
|
||||
padding: 1,2,1,2
|
||||
size_hint: 1,0.2
|
||||
DarkLabel:
|
||||
text: 'Analysis'
|
||||
size_hint: 1, 1
|
||||
multiline: False
|
||||
font_size: 0.45*self.size[1]
|
||||
StyledButton:
|
||||
id: analyze_extra
|
||||
text: 'Extra'
|
||||
tooltip_text: '[a]\nExtra analysis\nto increase accuracy\nof top move predictions\nand score difference'
|
||||
size_hint: 1, 1
|
||||
on_press: if not self.last_touch or self.last_touch.button=="left": root.katrain("analyze-extra","extra")
|
||||
StyledButton:
|
||||
id: analyze_equalize
|
||||
text: 'Equalize'
|
||||
tooltip_text: '[s]\nAnalysis of all\npredicted top moves\nwith the same accuracy'
|
||||
size_hint: 1, 1
|
||||
on_press: if not self.last_touch or self.last_touch.button=="left": root.katrain("analyze-extra","equalize")
|
||||
StyledButton:
|
||||
id: analyze_sweep
|
||||
text: 'Sweep'
|
||||
tooltip_text: '[d]\nFast analysis\nof all legal moves'
|
||||
size_hint: 1, 1
|
||||
on_press: if not self.last_touch or self.last_touch.button=="left": root.katrain("analyze-extra","sweep")
|
||||
ScoreGraph:
|
||||
id: graph
|
||||
size_hint: 1,0.7
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
size_hint: 1,0.3
|
||||
spacing: 3
|
||||
|
||||
BoxLayout: # ----------- play -----------
|
||||
orientation: 'vertical'
|
||||
size_hint: 1,1
|
||||
opacity: 1
|
||||
id: play_tab
|
||||
Label:
|
||||
size_hint: 1,0.02
|
||||
GridLayout:
|
||||
cols: 4
|
||||
rows: 2
|
||||
spacing: 1
|
||||
padding: 1
|
||||
size_hint: 1,0.125
|
||||
BlackCircleWithText:
|
||||
text: 'B'
|
||||
size_hint: 0.1, 1
|
||||
ToggleButtonContainer:
|
||||
size_hint: 0.6, 1
|
||||
id: B_player_mode
|
||||
options: PLAYER_MODE_VALUES
|
||||
labels: PLAYER_MODES
|
||||
tooltips: PLAYER_TOOLTIPS
|
||||
on_selection: root.katrain.update_state()
|
||||
StyledSpinner:
|
||||
id: B_AI_mode
|
||||
values: ['Default']
|
||||
size_hint: 0.3, 1
|
||||
on_text: if B_player_mode.children: B_player_mode.children[0].trigger_action(duration=0)
|
||||
Label:
|
||||
size_hint: None,1
|
||||
width: 3
|
||||
WhiteCircleWithText:
|
||||
text: 'W'
|
||||
size_hint: 0.1, 1
|
||||
ToggleButtonContainer:
|
||||
size_hint: 0.6, 1
|
||||
id: W_player_mode
|
||||
options: PLAYER_MODE_VALUES
|
||||
labels: PLAYER_MODES
|
||||
tooltips: PLAYER_TOOLTIPS
|
||||
on_selection: root.katrain.update_state()
|
||||
StyledSpinner:
|
||||
id: W_AI_mode
|
||||
size_hint: 0.3, 1
|
||||
values: ['Default']
|
||||
on_text: if W_player_mode.children: W_player_mode.children[0].trigger_action(duration=0)
|
||||
BoxLayout:
|
||||
size_hint: 1,0.05
|
||||
padding: 5,0
|
||||
spacing: 1
|
||||
StyledButton:
|
||||
text: 'Configure Teacher'
|
||||
tooltip_text: "Undos, dots, and\nSGF output settings"
|
||||
on_release: if not self.last_touch or self.last_touch.button=="left": root.configure_teacher()
|
||||
size_hint: 1.425, 1
|
||||
StyledButton:
|
||||
text: 'Configure AIs'
|
||||
tooltip_text: "Strength Settings per AI"
|
||||
on_release: if not self.last_touch or self.last_touch.button=="left": root.configure_ais()
|
||||
|
||||
Label:
|
||||
size_hint: 1,0.1
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
size_hint: 1, None
|
||||
spacing: 2
|
||||
padding: 2
|
||||
height: self.width * 0.25
|
||||
StyledIconButton:
|
||||
icon: 'img/flaticon/go-game.png'
|
||||
tooltip_text: '[Ctrl-N]\nNew Game'
|
||||
size_hint: 0.25, 1
|
||||
on_release: if not self.last_touch or self.last_touch.button=="left": root.katrain("new-game-popup")
|
||||
StyledIconButton:
|
||||
icon: 'img/flaticon/open-folder.png'
|
||||
tooltip_text: '[Ctrl-L]\nLoad Game'
|
||||
size_hint: 0.25, 1
|
||||
on_release: if not self.last_touch or self.last_touch.button=="left": root.katrain("analyze-sgf-popup")
|
||||
StyledIconButton:
|
||||
icon: 'img/flaticon/bar.png'
|
||||
tooltip_text: '[Ctrl-S]\nSave Game'
|
||||
size_hint: 0.25, 1
|
||||
on_release: if not self.last_touch or self.last_touch.button=="left": root.katrain('output-sgf')
|
||||
StyledIconButton:
|
||||
icon: 'img/flaticon/gears.png'
|
||||
tooltip_text: 'General Settings'
|
||||
size_hint: 0.25, 1
|
||||
on_release: if not self.last_touch or self.last_touch.button=="left": root.katrain('config-popup')
|
||||
|
||||
|
||||
<KaTrainGui>:
|
||||
board_gui: board_gui
|
||||
controls: controls
|
||||
board_controls: board_controls
|
||||
board_gui: board_gui
|
||||
controls: controls
|
||||
board_controls: board_controls
|
||||
analysis_controls: analysis_controls
|
||||
zen: False
|
||||
canvas.before:
|
||||
Color:
|
||||
rgb: BACKGROUND_COLOR
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
BoxLayout:
|
||||
width: 50
|
||||
size_hint: None,1
|
||||
orientation: 'vertical'
|
||||
Label:
|
||||
text: '='
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
AnalysisControls:
|
||||
id: analysis_controls
|
||||
size_hint_y: None
|
||||
height: min(100,self.width / 13.5)
|
||||
BadukPanWidget:
|
||||
id: board_gui
|
||||
BadukPanControls:
|
||||
id: board_controls
|
||||
size_hint_y: None
|
||||
height: min(100,self.width / 13.5)
|
||||
Controls:
|
||||
ControlsPanel:
|
||||
id: controls
|
||||
size_hint_x: None
|
||||
width: 0 if root.zen else self.height * 0.4
|
||||
opacity: 0 if root.zen else 1
|
||||
RightButtonControls:
|
||||
id: button_controls
|
||||
size_hint_x: None
|
||||
hidden: False
|
||||
width: 0 if self.hidden else self.height * 0.4
|
||||
opacity: 0 if self.hidden else 1
|
||||
width: 0 if root.zen else controls.width * 0.3
|
||||
opacity: 0 if root.zen else 1
|
||||
|
||||
<LoadSGFPopup>:
|
||||
fast: fast
|
||||
|
||||
+10
-7
@@ -157,7 +157,9 @@ class BadukPanWidget(Widget):
|
||||
self.gridpos_y = [self.pos[1] + extra_px_margin_y + math.floor((grid_spaces_margin_y[0] + i) * self.grid_size + 0.5) for i in range(board_size_y)]
|
||||
|
||||
Color(*self.ui_config["board_color"])
|
||||
Rectangle(pos=(self.gridpos_x[0]-self.grid_size*1.5,self.gridpos_y[0]-self.grid_size*1.5),size=(self.grid_size*x_grid_spaces,self.grid_size*y_grid_spaces) )
|
||||
Rectangle(
|
||||
pos=(self.gridpos_x[0] - self.grid_size * 1.5, self.gridpos_y[0] - self.grid_size * 1.5), size=(self.grid_size * x_grid_spaces, self.grid_size * y_grid_spaces)
|
||||
)
|
||||
|
||||
line_color = self.ui_config["line_color"]
|
||||
Color(*line_color)
|
||||
@@ -201,7 +203,7 @@ class BadukPanWidget(Widget):
|
||||
# stones
|
||||
current_node = katrain.game.current_node
|
||||
game_ended = katrain.game.ended
|
||||
full_eval_on = katrain.controls.eval.active
|
||||
full_eval_on = katrain.analysis_controls.eval.active
|
||||
has_stone = {}
|
||||
drawn_stone = {}
|
||||
for m in katrain.game.stones:
|
||||
@@ -241,7 +243,7 @@ class BadukPanWidget(Widget):
|
||||
|
||||
# ownership - allow one move out of date for smooth animation
|
||||
ownership = current_node.ownership or (current_node.parent and current_node.parent.ownership)
|
||||
if katrain.controls.ownership.active and ownership:
|
||||
if katrain.analysis_controls.ownership.active and ownership:
|
||||
ownership_grid = var_to_grid(ownership, (board_size_x, board_size_y))
|
||||
rsz = self.grid_size * 0.2
|
||||
for y in range(board_size_y - 1, -1, -1):
|
||||
@@ -257,7 +259,7 @@ class BadukPanWidget(Widget):
|
||||
|
||||
pass_btn = katrain.board_controls.pass_btn
|
||||
pass_btn.canvas.after.clear()
|
||||
if katrain.controls.policy.active and policy and not lock_ai:
|
||||
if katrain.analysis_controls.policy.active and policy and not lock_ai:
|
||||
policy_grid = var_to_grid(policy, (board_size_x, board_size_y))
|
||||
best_move_policy = max(*policy)
|
||||
for y in range(board_size_y - 1, -1, -1):
|
||||
@@ -304,7 +306,7 @@ class BadukPanWidget(Widget):
|
||||
self.active_pv_moves = []
|
||||
|
||||
# children of current moves in undo / review
|
||||
if katrain.controls.show_children.active:
|
||||
if katrain.analysis_controls.show_children.active:
|
||||
alpha = self.ui_config["ghost_alpha"]
|
||||
for child_node in current_node.children:
|
||||
points_lost = child_node.points_lost
|
||||
@@ -315,13 +317,14 @@ class BadukPanWidget(Widget):
|
||||
else:
|
||||
evalcol = copy.copy(self.eval_color(points_lost))
|
||||
evalcol[3] = alpha
|
||||
if ((teaching and child_node.auto_undo) or katrain.controls.play_analyze_mode == "analyze") and child_node.analysis_ready:
|
||||
# if ((teaching and child_node.auto_undo) or katrain.controls.play_analyze_mode == "analyze") and child_node.analysis_ready: # TODO:?
|
||||
if child_node.analysis_ready:
|
||||
self.active_pv_moves.append((move.coords, child_node.candidate_moves[0]["pv"], child_node))
|
||||
scale = self.ui_config["child_scale"]
|
||||
self.draw_stone(move.coords[0], move.coords[1], (*stone_color[move.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale)
|
||||
|
||||
# hints or PV
|
||||
if katrain.controls.hints.active and not game_ended and not lock_ai:
|
||||
if katrain.analysis_controls.hints.active and not game_ended and not lock_ai:
|
||||
hint_moves = current_node.candidate_moves
|
||||
for i, move_dict in enumerate(hint_moves):
|
||||
move = Move.from_gtp(move_dict["move"])
|
||||
|
||||
@@ -4,12 +4,13 @@ from kivy.clock import Clock
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.popup import Popup
|
||||
|
||||
from katrain.core.common import PLAYER_HUMAN, PLAYER_AI, PLAYER_HUMAN_TEACHING
|
||||
from katrain.gui.popups import ConfigAIPopup, ConfigTeacherPopup, ConfigTimerPopup
|
||||
|
||||
|
||||
class Controls(BoxLayout):
|
||||
class ControlsPanel(BoxLayout):
|
||||
def __init__(self, **kwargs):
|
||||
super(Controls, self).__init__(**kwargs)
|
||||
super(ControlsPanel, self).__init__(**kwargs)
|
||||
self.status = None
|
||||
self.status_node = None
|
||||
self.ai_settings_popup = None
|
||||
@@ -26,32 +27,18 @@ class Controls(BoxLayout):
|
||||
self.status_label.text = msg
|
||||
self.update_evaluation()
|
||||
|
||||
def select_mode(self, mode):
|
||||
if mode == "analyze":
|
||||
self.analyze_tab_button.trigger_action(duration=0)
|
||||
else:
|
||||
self.play_tab_button.trigger_action(duration=0)
|
||||
|
||||
@property
|
||||
def play_analyze_mode(self):
|
||||
if self.play_tab_button.state == "down":
|
||||
return "play"
|
||||
return "analyze"
|
||||
|
||||
def switch_mode(self):
|
||||
if self.play_analyze_mode == "play":
|
||||
self.select_mode("analyze")
|
||||
else:
|
||||
self.select_mode("play")
|
||||
return "analyze" # ??
|
||||
|
||||
def player_mode(self, player):
|
||||
return self.player_mode_groups[player].value
|
||||
return PLAYER_HUMAN
|
||||
|
||||
def ai_mode(self, player):
|
||||
return self.ai_mode_groups[player].text
|
||||
|
||||
def teaching_mode_enabled(self):
|
||||
return "undo" in self.player_mode("B") or "undo" in self.player_mode("W")
|
||||
return PLAYER_HUMAN_TEACHING in [self.player_mode("B"), self.player_mode("W")]
|
||||
|
||||
def on_size(self, *args):
|
||||
self.update_evaluation()
|
||||
@@ -69,33 +56,35 @@ class Controls(BoxLayout):
|
||||
|
||||
if current_node:
|
||||
move = current_node.move
|
||||
both_players_are_robots = "ai" in self.player_mode(current_node.player) and "ai" in self.player_mode(current_node.next_player)
|
||||
next_player_is_human_or_both_robots = current_node.player and ("ai" not in self.player_mode(current_node.player) or both_players_are_robots)
|
||||
current_player_is_ai_playing_human = current_node.player and "ai" in self.player_mode(current_node.player) and "ai" not in self.player_mode(current_node.next_player)
|
||||
both_players_are_robots = self.player_mode(current_node.player) == PLAYER_AI and self.player_mode(current_node.next_player) == PLAYER_AI
|
||||
next_player_is_human_or_both_robots = current_node.player and (self.player_mode(current_node.player) == PLAYER_AI or both_players_are_robots)
|
||||
current_player_is_ai_playing_human = (
|
||||
current_node.player and self.player_mode(current_node.player) == PLAYER_AI and self.player_mode(current_node.next_player) != PLAYER_AI
|
||||
)
|
||||
if next_player_is_human_or_both_robots and not current_node.is_root and move:
|
||||
info += current_node.comment(teach="undo" in self.player_mode(current_node.player), hints=self.hints.active)
|
||||
info += current_node.comment(teach=self.player_mode(current_node.player) == PLAYER_HUMAN_TEACHING, hints=self.hints.active)
|
||||
self.active_comment_node = current_node
|
||||
elif current_player_is_ai_playing_human and current_node.parent:
|
||||
info += current_node.parent.comment(teach="undo" in self.player_mode(current_node.next_player), hints=self.hints.active)
|
||||
info += current_node.parent.comment(teach=self.player_mode(current_node.next_player) == PLAYER_HUMAN_TEACHING, hints=self.hints.active)
|
||||
self.active_comment_node = current_node.parent
|
||||
|
||||
if current_node.analysis_ready:
|
||||
self.score.text = current_node.format_score()
|
||||
self.win_rate.text = current_node.format_win_rate()
|
||||
self.stats.score.text = current_node.format_score()
|
||||
self.stats.win_rate.text = current_node.format_win_rate()
|
||||
if move and next_player_is_human_or_both_robots: # don't immediately hide this when an ai moves comes in
|
||||
points_lost = current_node.points_lost
|
||||
self.score_change.label = f"Points lost" if points_lost and points_lost > 0 else f"Points gained"
|
||||
self.score_change.text = f"{move.player}: {abs(points_lost):.1f}" if points_lost else "-"
|
||||
self.stats.score_change.label = f"Points lost" if points_lost and points_lost > 0 else f"Points gained"
|
||||
self.stats.score_change.text = f"{move.player}: {abs(points_lost):.1f}" if points_lost else "-"
|
||||
elif not current_player_is_ai_playing_human:
|
||||
self.score_change.label = f"Points lost"
|
||||
self.score_change.text = "-"
|
||||
self.stats.score_change.label = f"Points lost"
|
||||
self.stats.score_change.text = "-"
|
||||
elif current_player_is_ai_playing_human and current_node.parent and current_node.parent.move:
|
||||
points_lost = current_node.parent.points_lost
|
||||
self.score_change.label = f"Points lost" if points_lost and points_lost > 0 else f"Points gained"
|
||||
self.score_change.text = f"{current_node.parent.move.player}: {abs(points_lost):.1f}" if points_lost else "-"
|
||||
self.stats.score_change.label = f"Points lost" if points_lost and points_lost > 0 else f"Points gained"
|
||||
self.stats.score_change.text = f"{current_node.parent.move.player}: {abs(points_lost):.1f}" if points_lost else "-"
|
||||
elif both_players_are_robots and current_node.parent and current_node.parent.analysis_ready:
|
||||
self.score.text = current_node.parent.format_score()
|
||||
self.win_rate.text = current_node.parent.format_win_rate()
|
||||
self.stats.score.text = current_node.parent.format_score()
|
||||
self.stats.win_rate.text = current_node.parent.format_win_rate()
|
||||
|
||||
self.graph.update_value(current_node)
|
||||
self.note.text = current_node.note
|
||||
@@ -122,8 +111,8 @@ class Controls(BoxLayout):
|
||||
player = current_node.next_player
|
||||
byo_len = max(1, self.katrain.config("timer/byo_length"))
|
||||
byo_num = max(1, self.katrain.config("timer/byo_num"))
|
||||
ai = "ai" in self.player_mode(player)
|
||||
if not self.pause.state == "down" and not ai:
|
||||
ai = self.player_mode(player) == PLAYER_AI
|
||||
if not self.timer.paused and not ai:
|
||||
if last_update_node == current_node and not current_node.children:
|
||||
current_node.time_used += now - last_update_time
|
||||
else:
|
||||
@@ -133,20 +122,9 @@ class Controls(BoxLayout):
|
||||
current_node.time_used -= byo_len
|
||||
time_remaining += byo_len
|
||||
self.periods_used[player] += 1
|
||||
if not ai:
|
||||
time_remaining = byo_len - current_node.time_used
|
||||
periods_rem = byo_num - self.periods_used[player]
|
||||
col = "#444" if self.pause.state == "down" else "#111"
|
||||
else:
|
||||
time_remaining, periods_rem = 59.59, 1
|
||||
col = "#444"
|
||||
if periods_rem > 0:
|
||||
self.timer.text = f"[color={col}]{time_remaining:05.2f}[/color]".replace(".", ":")
|
||||
self.periods.text = f"x{periods_rem}"
|
||||
else:
|
||||
self.timer.text = f"[color=#b22]00:00[/color]"
|
||||
self.periods.text = f"[color=#b22]x0[/color]"
|
||||
self.pause.state = "down"
|
||||
time_remaining = byo_len - current_node.time_used
|
||||
periods_rem = byo_num - self.periods_used[player]
|
||||
self.timer.state = (time_remaining, periods_rem, ai)
|
||||
|
||||
def configure_timer(self):
|
||||
self.pause.state = "down"
|
||||
@@ -260,7 +260,7 @@ class CensorableLabel(BoxLayout):
|
||||
label = StringProperty("")
|
||||
|
||||
|
||||
class ScoreGraph(Label):
|
||||
class ScoreGraph(Label, BackgroundColor):
|
||||
nodes = ListProperty([])
|
||||
line_points = ListProperty([])
|
||||
dot_pos = ListProperty([0, 0])
|
||||
|
||||
@@ -38,8 +38,7 @@ setup(
|
||||
install_requires=[
|
||||
"kivy>=1.11 ;python_version<'3.8'",
|
||||
"kivy>=2.0.0rc2 ;python_version>='3.8'",
|
||||
"kivymd>=0.104.0"
|
||||
"kivy_deps.glew;platform_system=='Windows'",
|
||||
"kivymd>=0.104.0" "kivy_deps.glew;platform_system=='Windows'",
|
||||
"kivy_deps.sdl2;platform_system=='Windows'",
|
||||
"kivy_deps.gstreamer;platform_system=='Windows'",
|
||||
"importlib_resources ;python_version<'3.7'",
|
||||
|
||||
Reference in new issue
Block a user