ui fiddling
This commit is contained in:
1 parent
3ea068ccd0
commit
d14f077e71
16 files changed
+434
-294
No files matched your search
+1
-13
@@ -1,16 +1,4 @@
|
||||
from gui.badukpan import BadukPanWidget, BadukPanControls
|
||||
from gui.controls import Controls
|
||||
from gui.kivyutils import (
|
||||
BWCheckBoxHint,
|
||||
CensorableLabel,
|
||||
CheckBoxHint,
|
||||
LabelledFloatInput,
|
||||
LabelledIntInput,
|
||||
StyledButton,
|
||||
DarkLabel,
|
||||
BaseCircleWithText,
|
||||
LabelledSpinner,
|
||||
LabelledCheckBox,
|
||||
LabelledTextInput,
|
||||
)
|
||||
from gui.kivyutils import *
|
||||
from gui.popups import LoadSGFPopup
|
||||
+16
-13
@@ -59,9 +59,8 @@ class BadukPanWidget(Widget):
|
||||
if nodes_here and max(yd, xd) < self.grid_size / 2: # load old comment
|
||||
katrain.log(f"\nAnalysis:\n{nodes_here[-1].analysis}", OUTPUT_DEBUG)
|
||||
katrain.log(f"\nParent Analysis:\n{nodes_here[-1].parent.analysis}", OUTPUT_DEBUG)
|
||||
if not katrain.controls.ai_lock.active:
|
||||
katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
|
||||
katrain.controls.show_evaluation_stats(nodes_here[-1])
|
||||
katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
|
||||
katrain.controls.show_evaluation_stats(nodes_here[-1])
|
||||
|
||||
self.ghost_stone = None
|
||||
self.draw_board_contents() # remove ghost
|
||||
@@ -148,7 +147,8 @@ class BadukPanWidget(Widget):
|
||||
# stones
|
||||
current_node = katrain.game.current_node
|
||||
next_player = katrain.game.next_player
|
||||
full_eval_on = katrain.controls.eval.active_map
|
||||
game_ended = katrain.game.game_ended
|
||||
full_eval_on = katrain.controls.eval.active
|
||||
has_stone = {}
|
||||
drawn_stone = {}
|
||||
for m in katrain.game.stones:
|
||||
@@ -161,7 +161,7 @@ class BadukPanWidget(Widget):
|
||||
evalsize = 1
|
||||
for m in node.move_with_placements:
|
||||
if has_stone.get(m.coords) and not drawn_stone.get(m.coords): # skip captures, last only for
|
||||
move_eval_on = full_eval_on[m.player] or i < show_n_eval
|
||||
move_eval_on = full_eval_on or i < show_n_eval
|
||||
evalcol = self.eval_color(points_lost) if move_eval_on and points_lost is not None else None
|
||||
inner = stone_color[m.opponent] if i == 0 else None
|
||||
drawn_stone[m.coords] = m.player
|
||||
@@ -181,7 +181,7 @@ class BadukPanWidget(Widget):
|
||||
ix = ix + 1
|
||||
|
||||
policy = current_node.policy
|
||||
if not policy and current_node.parent and current_node.parent.policy and 'ai' in katrain.controls.player_mode('B') and 'ai' in katrain.controls.player_mode('W'):
|
||||
if not policy and current_node.parent and current_node.parent.policy and "ai" in katrain.controls.player_mode("B") and "ai" in katrain.controls.player_mode("W"):
|
||||
policy = current_node.parent.policy # in the case of AI self-play we allow the policy to be one step out of date
|
||||
pass_btn = katrain.board_controls.pass_btn
|
||||
pass_btn.canvas.after.clear()
|
||||
@@ -192,7 +192,10 @@ class BadukPanWidget(Widget):
|
||||
for x in range(board_size):
|
||||
if policy[ix] > 0:
|
||||
polsize = math.sqrt(policy[ix])
|
||||
policy_circle_color = (*self.ui_config["policy_color"], self.ui_config["ghost_alpha"] + self.ui_config["top_move_x_alpha"] * (policy[ix] == best_move_policy))
|
||||
policy_circle_color = (
|
||||
*self.ui_config["policy_color"],
|
||||
self.ui_config["ghost_alpha"] + self.ui_config["top_move_x_alpha"] * (policy[ix] == best_move_policy),
|
||||
)
|
||||
self.draw_stone(x, y, policy_circle_color, scale=polsize)
|
||||
ix = ix + 1
|
||||
polsize = math.sqrt(policy[ix])
|
||||
@@ -212,17 +215,17 @@ class BadukPanWidget(Widget):
|
||||
self.draw_stone(m.coords[0], m.coords[1], (*stone_color[m.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale)
|
||||
|
||||
# hints
|
||||
if katrain.controls.hints.active(next_player):
|
||||
if katrain.controls.hints.active and not game_ended:
|
||||
hint_moves = current_node.candidate_moves
|
||||
for i, d in enumerate(hint_moves):
|
||||
move = Move.from_gtp(d["move"])
|
||||
if move.coords is not None and move.coords not in undo_coords:
|
||||
alpha, scale = self.ui_config["ghost_alpha"], 1.0
|
||||
alpha, scale = self.ui_config["ghost_alpha"], 1.0
|
||||
if i == 0:
|
||||
c[3] += self.ui_config["top_move_x_alpha"]
|
||||
elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config?
|
||||
alpha += self.ui_config["top_move_x_alpha"]
|
||||
elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config?
|
||||
scale = 0.8
|
||||
self.draw_stone(move.coords[0], move.coords[1], [*self.eval_color(d["pointsLost"]),alpha], scale=scale)
|
||||
self.draw_stone(move.coords[0], move.coords[1], [*self.eval_color(d["pointsLost"]), alpha], scale=scale)
|
||||
|
||||
# hover next move ghost stone
|
||||
if self.ghost_stone:
|
||||
@@ -231,7 +234,7 @@ class BadukPanWidget(Widget):
|
||||
# pass circle
|
||||
passed = len(nodes) > 1 and current_node.is_pass
|
||||
if passed:
|
||||
if katrain.game.game_ended:
|
||||
if game_ended:
|
||||
text = "game\nend"
|
||||
else:
|
||||
text = "pass"
|
||||
|
||||
+9
-24
@@ -1,6 +1,6 @@
|
||||
from kivy.graphics.vertex_instructions import SmoothLine, Line
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.graphics.context_instructions import Color
|
||||
from kivy.graphics.vertex_instructions import Line, SmoothLine
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
|
||||
class Controls(BoxLayout):
|
||||
@@ -41,11 +41,8 @@ class Controls(BoxLayout):
|
||||
def player_mode(self, player):
|
||||
return self.player_mode_groups[player].value
|
||||
|
||||
def unlock(self):
|
||||
if self.ai_lock.active:
|
||||
self.ai_lock.checkbox.trigger_action(duration=0)
|
||||
for el in [self.ai_lock.checkbox, self.analyze_tab_button, self.ai_auto.white, self.ai_auto.black, self.ai_move]:
|
||||
el.disabled = False
|
||||
def ai_mode(self, player):
|
||||
return self.ai_mode_groups[player].text.lower()
|
||||
|
||||
def on_size(self, *args):
|
||||
self.update_evaluation()
|
||||
@@ -63,26 +60,14 @@ class Controls(BoxLayout):
|
||||
|
||||
if current_node:
|
||||
move = current_node.single_move
|
||||
current_player_is_human_or_both_robots = not current_node.player or not self.ai_auto.active(current_node.player) or self.ai_auto.active(current_node.next_player)
|
||||
current_player_is_human_or_both_robots = (
|
||||
not current_node.player or "ai" not in self.player_mode(current_node.player) or "ai" in self.player_mode(current_node.next_player)
|
||||
)
|
||||
if current_player_is_human_or_both_robots and not current_node.is_root and move:
|
||||
info += current_node.comment(eval=True, hints=self.hints.active(move.player))
|
||||
info += current_node.comment(eval=True, hints=self.hints.active)
|
||||
if current_player_is_human_or_both_robots:
|
||||
self.show_evaluation_stats(current_node)
|
||||
|
||||
game_node = katrain.game.current_node
|
||||
scores = [n.score for n in game_node.nodes_from_root]
|
||||
# TODO: like redo, what is the node to redo / should we append? cache?
|
||||
self.graph.canvas.clear()
|
||||
with self.graph.canvas:
|
||||
pt = []
|
||||
nnscores = [s for s in scores if s is not None] + [-5, 5]
|
||||
scale = max(max(*nnscores), -min(*nnscores)) * 1.05
|
||||
xscale = self.graph.width * 0.9 / max(len(scores), 20)
|
||||
ls = 0
|
||||
for i, s in enumerate(scores):
|
||||
ls = s or ls
|
||||
pt.extend([self.graph.pos[0] + 0.05 * self.graph.width + i * xscale, self.graph.pos[1] + self.graph.height / 2 * (1 + ls / scale)])
|
||||
Color(0, 0, 0)
|
||||
Line(points=pt, width=1.0) # just set points?
|
||||
self.graph.update_value(current_node.depth, current_node.score)
|
||||
|
||||
self.info.text = info
|
||||
+55
-24
@@ -10,6 +10,7 @@ import re
|
||||
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.checkbox import CheckBox
|
||||
from kivy.uix.dropdown import DropDown
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.spinner import Spinner
|
||||
@@ -32,13 +33,21 @@ class DarkLabel(Label):
|
||||
|
||||
|
||||
class StyledButton(Button):
|
||||
pass
|
||||
text_color = ListProperty([0.95, 0.95, 0.95, 1]) # TODO defaults as in ..
|
||||
button_color = ListProperty([0.21, 0.28, 0.31, 1])
|
||||
button_color_down = ListProperty([0.105, 0.14, 0.155, 1])
|
||||
margin = ListProperty([1, 1, 1, 1]) # margin left bottom right top
|
||||
radius = ListProperty((0,))
|
||||
|
||||
|
||||
class StyledToggleButton(StyledButton, ToggleButtonBehavior):
|
||||
value = StringProperty("")
|
||||
|
||||
|
||||
class StyledSpinner(Spinner):
|
||||
pass
|
||||
|
||||
|
||||
class ToggleButtonContainer(GridLayout):
|
||||
__events__ = ("on_selection",)
|
||||
|
||||
@@ -47,8 +56,7 @@ class ToggleButtonContainer(GridLayout):
|
||||
selected = StringProperty("")
|
||||
group = StringProperty(None)
|
||||
autosize = BooleanProperty(True)
|
||||
button_class = ObjectProperty(StyledToggleButton)
|
||||
margin = ListProperty([1, 1, 0, 0])
|
||||
margin = ListProperty([0, 0, 1, 1]) # TODO outer vs inner margin?
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -66,20 +74,19 @@ class ToggleButtonContainer(GridLayout):
|
||||
if len(self.labels) < len(self.options):
|
||||
self.labels += self.options[len(self.labels) + 1 :]
|
||||
|
||||
def state_handler(btn,*args):
|
||||
def state_handler(btn, *args):
|
||||
self.dispatch("on_selection")
|
||||
btn.state = 'down' # no toggle
|
||||
btn.state = "down" # no toggle
|
||||
|
||||
for i, opt in enumerate(self.options):
|
||||
state = "down" if opt == self.selected else "normal"
|
||||
self.add_widget(self.button_class(group=self.group, text=self.labels[i], value=opt, state=state,
|
||||
margin=self.margin,on_press=state_handler))
|
||||
self.add_widget(StyledToggleButton(group=self.group, text=self.labels[i], value=opt, state=state, margin=self.margin, on_press=state_handler,))
|
||||
Clock.schedule_once(self._size, 0)
|
||||
|
||||
def _size(self, _dt):
|
||||
if self.autosize:
|
||||
for tb in self.children:
|
||||
tb.size_hint = (tb.texture_size[0] + 3, 1)
|
||||
tb.size_hint = (tb.texture_size[0] + 25, 1)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
@@ -96,6 +103,7 @@ class BaseCircleWithText(DarkLabel):
|
||||
|
||||
class LabelledTextInput(TextInput):
|
||||
input_property = StringProperty("")
|
||||
multiline = BooleanProperty(False)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
@@ -115,7 +123,7 @@ class LabelledCheckBox(CheckBox):
|
||||
return bool(self.active)
|
||||
|
||||
|
||||
class LabelledSpinner(Spinner):
|
||||
class LabelledSpinner(StyledSpinner):
|
||||
input_property = StringProperty("")
|
||||
|
||||
@property
|
||||
@@ -156,30 +164,53 @@ class LabelledIntInput(LabelledTextInput):
|
||||
return int(self.text)
|
||||
|
||||
|
||||
class BWCheckBoxHint(BoxLayout):
|
||||
__events__ = ("on_active",)
|
||||
|
||||
@property
|
||||
def active_map(self):
|
||||
return {"B": self.black.active, "W": self.white.active}
|
||||
|
||||
def active(self, player):
|
||||
return self.active_map[player]
|
||||
|
||||
def on_active(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
class CensorableLabel(BoxLayout):
|
||||
@property
|
||||
def text(self):
|
||||
return self.value.text
|
||||
|
||||
|
||||
class ScoreGraph(Label):
|
||||
values = ListProperty([])
|
||||
line_points = ListProperty([])
|
||||
line = ObjectProperty(None)
|
||||
dot = ObjectProperty(None)
|
||||
highlighted_index = NumericProperty(0)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
with self.canvas:
|
||||
Color(0, 0, 0)
|
||||
self.line = Line(points=self.line_points, width=1.0)
|
||||
Color(0.21, 0.28, 0.31)
|
||||
self.dot = Ellipse(pos=self.pos, size=(2, 2))
|
||||
|
||||
def clear_values(self):
|
||||
self.values = []
|
||||
|
||||
def on_size(self, *args):
|
||||
if self.values:
|
||||
val_range = min(self.values), max(self.values)
|
||||
scale = max(5, max(-val_range[0], val_range[1]) * 1.05)
|
||||
xscale = self.width * 0.9 / max(len(self.values), 20)
|
||||
self.line_points = [[self.pos[0] + 0.05 * self.width + i * xscale, self.pos[1] + self.height / 2 * (1 + val / scale)] for i, val in enumerate(self.values)]
|
||||
self.line.points = sum(self.line_points, [])
|
||||
if self.highlighted_index:
|
||||
self.dot.pos = [c - 1 for c in self.line_points[self.highlighted_index]]
|
||||
|
||||
def update_value(self, index, value):
|
||||
self.values.extend([0] * max(0, index - (len(self.values) - 1)))
|
||||
self.values[index] = value or 0
|
||||
self.highlighted_index = index
|
||||
self.on_size()
|
||||
|
||||
|
||||
def draw_text(pos, text, **kw):
|
||||
label = CoreLabel(text=text, bold=True, **kw)
|
||||
label.refresh()
|
||||
Rectangle(texture=label.texture, pos=(pos[0] - label.texture.size[0] / 2, pos[1] - label.texture.size[1] / 2), size=label.texture.size)
|
||||
Rectangle(
|
||||
texture=label.texture, pos=(pos[0] - label.texture.size[0] / 2, pos[1] - label.texture.size[1] / 2), size=label.texture.size,
|
||||
)
|
||||
|
||||
|
||||
def draw_circle(pos, r, col):
|
||||
|
||||
+31
-14
@@ -1,3 +1,5 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
import os
|
||||
from constants import OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
@@ -76,13 +78,15 @@ class ConfigPopup(QuickConfigGui):
|
||||
else:
|
||||
return LabelledTextInput
|
||||
|
||||
def __init__(self, katrain, popup, config):
|
||||
def __init__(self, katrain, popup, config, ignore_cats):
|
||||
self.config = config
|
||||
self.orientation = "vertical"
|
||||
super().__init__(katrain, popup)
|
||||
cols = [BoxLayout(orientation="vertical"), BoxLayout(orientation="vertical")]
|
||||
props_in_col = [0, 0]
|
||||
for k1, all_d in config.items():
|
||||
if k1 in ignore_cats:
|
||||
continue
|
||||
d = {k: v for k, v in all_d.items() if isinstance(v, (int, float, str, bool))} # no complex objects
|
||||
cat = GridLayout(cols=2, rows=len(d) + 1, size_hint=(1, len(d) + 1))
|
||||
cat.add_widget(Label(text=""))
|
||||
@@ -101,22 +105,27 @@ class ConfigPopup(QuickConfigGui):
|
||||
col_container.add_widget(cols[0])
|
||||
col_container.add_widget(cols[1])
|
||||
self.add_widget(col_container)
|
||||
self.save_button = StyledButton(text="Apply Settings", on_press=lambda _: self.update_config(), size_hint=(1, 0.05))
|
||||
self.save_button = StyledButton(text="Apply and Save Settings", on_press=lambda _: self.update_config(save_to_file=True), size_hint=(1, 0.05))
|
||||
self.add_widget(self.save_button)
|
||||
self.info_label = Label()
|
||||
self.apply_button = StyledButton(text="Apply Settings", on_press=lambda _: self.update_config())
|
||||
self.save_button = StyledButton(text="Apply and Save Settings", on_press=lambda _: self.update_config(save_to_file=True))
|
||||
btn_container = BoxLayout(orientation="horizontal", size_hint=(1, 0.05))
|
||||
btn_container.add_widget(self.info_label)
|
||||
btn_container.add_widget(self.apply_button)
|
||||
btn_container.add_widget(self.save_button)
|
||||
self.add_widget(btn_container)
|
||||
|
||||
def update_config(self, save_to_file=False):
|
||||
updated_cat = []
|
||||
updated_cat = defaultdict(list)
|
||||
try:
|
||||
for k, v in self.collect_properties(self).items():
|
||||
k1, k2 = k.split("/")
|
||||
if self.config[k1][k2] != v:
|
||||
self.katrain.log(f"Updating setting {k} = {v}", OUTPUT_DEBUG)
|
||||
updated_cat.append(k1)
|
||||
updated_cat[k1].append(k2)
|
||||
self.config[k1][k2] = v
|
||||
self.popup.dismiss()
|
||||
except InputParseError as e:
|
||||
self.save_button.text = str(e) # TODO: nicer error
|
||||
self.info_label.text = str(e)
|
||||
self.katrain.log(e, OUTPUT_ERROR)
|
||||
return
|
||||
|
||||
@@ -124,10 +133,18 @@ class ConfigPopup(QuickConfigGui):
|
||||
for cat in updated_cat:
|
||||
self.katrain.save_config(cat, **self.config[cat])
|
||||
|
||||
if "engine" in updated_cat:
|
||||
self.katrain.log("Restarting Engine after settings change")
|
||||
old_engine = self.katrain.engine
|
||||
self.katrain.engine = KataGoEngine(self.katrain, self.config["engine"])
|
||||
self.katrain.game.engine = self.katrain.engine
|
||||
old_engine.shutdown(finish=True)
|
||||
self.katrain.update_state(redraw_board=True)
|
||||
engine_restart = False
|
||||
for cat, updates in updated_cat.items():
|
||||
if "engine" in cat: # TODO: multi engine support
|
||||
if "visits" in updates:
|
||||
self.katrain.engine.visits = self.config[cat]["visits"]
|
||||
if set(updates) != {"visits"}:
|
||||
self.katrain.log(f"Restarting Engine {cat} after {updates} settings change")
|
||||
old_engine = self.katrain.engine
|
||||
self.katrain.engine = KataGoEngine(self.katrain, self.config[cat])
|
||||
self.katrain.game.engine = self.katrain.engine
|
||||
old_engine.shutdown(finish=True)
|
||||
engine_restart = True
|
||||
|
||||
if engine_restart:
|
||||
self.katrain.update_state(redraw_board=True)
|
||||
Reference in new issue
Block a user