ui fiddling

This commit is contained in:
Sander Land committed 2020-04-21 16:54:55 +02:00
1 parent 3ea068ccd0
commit d14f077e71
16 files changed
+434 -294

No files matched your search

+1 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)