Browse Source

feat: implement an unordered but animated version of the linear results

The data is a dummy, waiting to be connected to a provider like Twitch.
We also need to sort the profiles, and animate the sort ;)

The GUI elements will probably need more work as well (like clipping, etc.)
master
Dominique Merle 2 years ago
parent
commit
4e05835e81
  1. 18
      addons/majority_judgment/MajorityJudgmentCandidateMeritProfile.gd
  2. 1
      addons/majority_judgment/MajorityJudgmentCandidateTally.gd
  3. 9
      addons/majority_judgment/MajorityJudgmentGrade.gd
  4. 38
      addons/majority_judgment/MajorityJudgmentGrading.gd
  5. 25
      addons/majority_judgment/MajorityJudgmentPoll.gd
  6. 7
      addons/majority_judgment/MajorityJudgmentPollTally.gd
  7. 8
      addons/majority_judgment/nodes/MajorityJudgmentLinearGradeNode.tscn
  8. 180
      addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.gd
  9. 78
      addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.svg
  10. 34
      addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.svg.import
  11. 22
      addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.tscn
  12. 113
      addons/majority_judgment/nodes/MajorityJudgmentMeritProfileControl.gd
  13. 13
      addons/majority_judgment/nodes/MajorityJudgmentMeritProfileControl.tscn
  14. 3
      core/App.gd
  15. 7
      gui/forms/NewPollForm.gd

18
addons/majority_judgment/MajorityJudgmentCandidateMeritProfile.gd

@ -8,8 +8,6 @@ by putting on a single line the judgments they received,
sorted by increasing (or decreasing) grade.
This is built by the Talliers.
Overzealous object-oriented approach yet again, perhaps.
"""
@ -19,6 +17,7 @@ export(Resource) var candidate_tally #:MajorityJudgmentCandidateTally
# For each grade (worst to best), the amount of judgments received
export(Array, int) var grades
export(Array, Color) var colors
#export(int) var grades_amount
@ -30,6 +29,14 @@ func count_judgments() -> int:
return amount
func count_grades_with_judgments() -> int:
var amount := 0
for amount_for_grade in self.grades:
if amount_for_grade:
amount += 1
return amount
func get_median() -> int:
"""
Returns the grade index, not a Grade instance.
@ -65,3 +72,10 @@ func remove_one_judgment(grade_index:int) -> void:
assert(0 < self.grades[grade_index])
self.grades[grade_index] -= 1
func get_color_of_grade(grade_index:int) -> Color:
if self.colors:
return self.colors[grade_index]
if self.candidate_tally and self.candidate_tally.poll:
return self.candidate_tally.poll.grading.get_color_of_grade(grade_index)
return Color.blue

1
addons/majority_judgment/MajorityJudgmentCandidateTally.gd

@ -10,3 +10,4 @@ export(int) var creation_timestamp:int
export(Resource) var median_grade
export(int) var position:int

9
addons/majority_judgment/MajorityJudgmentGrade.gd

@ -3,14 +3,16 @@ class_name MajorityJudgmentGrade
"""
A single grade.
A single grade, like 'Excellent' or 'To Reject'.
Could be just an int or a string,
but let's go full oriented object.
Each grade will also handle its color, I think.
PERHAPS ALREADY DEPRECATED
L10N PERHAPS ALREADY DEPRECATED ---------------
Supports localization (l10n).
Default language is Esperanto. Videoludemuloj!
-----------------------------------------------
"""
@ -19,6 +21,7 @@ Default language is Esperanto. Videoludemuloj!
export(Dictionary) var localized_names:Dictionary
# (WiP: perhaps not used right now, but it is set)
# This is set by the gradation this grade is included in
# 0: worst grade
# more: better grade (up to gradation size minus one)

38
addons/majority_judgment/MajorityJudgmentGrading.gd

@ -3,6 +3,7 @@ class_name MajorityJudgmentGrading
"""
An ordered list of grades, from worst to best.
It's VERY IMPORTANT that the grades be NON-AMBIGUOUS,
that everyone would sort the grades in the same order
if they were given shuffled.
@ -14,11 +15,48 @@ if they were given shuffled.
export(Array) var grades:Array setget set_grades, get_grades
static func make_empty():
return load("res://addons/majority_judgment/MajorityJudgmentGrading.gd").new()
static func make_quality_6():
var grading = make_empty()
grading.set_grades([
MajorityJudgmentGrade.make("To Reject"),
MajorityJudgmentGrade.make("Poor"),
MajorityJudgmentGrade.make("Passable"),
MajorityJudgmentGrade.make("Good"),
MajorityJudgmentGrade.make("Very Good"),
MajorityJudgmentGrade.make("Excellent"),
])
return grading
func set_grades(_grades:Array):
assert(_grades, "Provide an array of MajorityJudgmentGrade")
grades = _grades
for i in range(grades.size()):
grades[i].worth = i
func get_grades() -> Array:
return grades
func get_colors() -> Array:
assert(grades.size() == 6, "WIP")
return [
Color("#e0361c"),
Color("#ee6e00"),
Color("#fdb200"),
Color("#c6d700"),
Color("#7ec239"),
Color("#02ab58"),
# Color("#007e3d"),
]
func get_color_of_grade(grade_index:int):
var colors = get_colors()
grade_index = clamp(grade_index, 0, colors.size()-1)
return colors[grade_index]

25
addons/majority_judgment/MajorityJudgmentPoll.gd

@ -44,7 +44,13 @@ func _init():
# Instead, use a factory approach
#static func make(__title, __grading):
static func make(__title, __grading, __candidates):
var poll = load("res://addons/majority_judgment/MajorityJudgmentPoll.gd").new()
poll.set_title(__title)
poll.set_grading(__grading)
poll.set_candidates(__candidates)
return poll
func set_title(__title:String) -> void:
@ -82,6 +88,10 @@ func get_candidates() -> Array:
return candidates
func has_judgments() -> bool:
return 0 < judgments.size()
func set_judgments(__judgments:Array) -> void:
judgments = __judgments
@ -106,6 +116,8 @@ func get_judgments() -> Array:
func tally() -> MajorityJudgmentPollTally:
if not has_judgments():
return null
# Pick relevant tallier from settings later on
var tallier = MajorityJudgmentEasyTallier.new()
# var tallier = MajorityJudgmentLiquidTallier.new()
@ -119,8 +131,17 @@ func get_participants() -> Array:
if not participants.has(participant):
participants.append(participant)
return participants
func count_participants() -> int:
return get_participants().size()
func get_dummy_merit_profile(candidate):
var merit = MajorityJudgmentCandidateMeritProfile.new()
merit.grades = Array()
for i in range(get_grading().grades.size()):
merit.grades.append(1)
merit.colors = get_grading().get_colors()
return merit

7
addons/majority_judgment/MajorityJudgmentPollTally.gd

@ -19,3 +19,10 @@ export(Resource) var poll #:MajorityJudgmentPoll
# so check each MajorityJudgmentCandidateTally.position.
export(Array, Resource) var candidates_tallies:Array
func get_tally_of_candidate(candidate:MajorityJudgmentCandidate) -> MajorityJudgmentCandidateTally:
for candidate_tally in self.candidates_tallies:
if candidate_tally.candidate == candidate:
return candidate_tally
assert(false, "Candidate tally not found.")
return MajorityJudgmentCandidateTally.new() # urgh

8
addons/majority_judgment/nodes/MajorityJudgmentLinearGradeNode.tscn

@ -0,0 +1,8 @@
[gd_scene format=2]
[node name="MajorityJudgmentLinearGradeNode" type="ColorRect"]
margin_right = 40.0
margin_bottom = 40.0
__meta__ = {
"_edit_use_anchors_": false
}

180
addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.gd

@ -0,0 +1,180 @@
extends Control
const MeritProfileScene = preload("res://addons/majority_judgment/nodes/MajorityJudgmentMeritProfileControl.tscn")
export(Resource) var poll setget set_poll, get_poll
#func _ready():
# set_poll(load("res://test_poll.tres"))
func get_poll() -> MajorityJudgmentPoll:
return poll
func set_poll(__poll:MajorityJudgmentPoll):
poll = __poll
craft_nodes()
update_nodes()
yield(get_tree().create_timer(3), "timeout")
var j = MajorityJudgmentJudgment.new()
j.set_participant(MajorityJudgmentParticipant.make("Plume"))
j.set_grade(poll.grading.grades[2])
j.set_candidate(poll.candidates[0])
poll.add_judgment(j)
var sylvain = MajorityJudgmentParticipant.make("Sylvain")
j = MajorityJudgmentJudgment.new()
j.set_participant(sylvain)
j.set_grade(poll.grading.grades[3])
j.set_candidate(poll.candidates[0])
poll.add_judgment(j)
j = MajorityJudgmentJudgment.new()
j.set_participant(sylvain)
j.set_grade(poll.grading.grades[3])
j.set_candidate(poll.candidates[1])
poll.add_judgment(j)
j = MajorityJudgmentJudgment.new()
j.set_participant(sylvain)
j.set_grade(poll.grading.grades[4])
j.set_candidate(poll.candidates[2])
poll.add_judgment(j)
j = MajorityJudgmentJudgment.new()
j.set_participant(MajorityJudgmentParticipant.make("Sabre"))
j.set_grade(poll.grading.grades[5])
j.set_candidate(poll.candidates[0])
poll.add_judgment(j)
update_nodes()
yield(get_tree().create_timer(2), "timeout")
var tiger = MajorityJudgmentParticipant.make("Tiger")
j = MajorityJudgmentJudgment.new()
j.set_participant(tiger)
j.set_grade(poll.grading.grades[4])
j.set_candidate(poll.candidates[0])
poll.add_judgment(j)
update_nodes()
yield(get_tree().create_timer(2), "timeout")
j = MajorityJudgmentJudgment.new()
j.set_participant(tiger)
j.set_grade(poll.grading.grades[1])
j.set_candidate(poll.candidates[2])
poll.add_judgment(j)
update_nodes()
var profiles_nodes := Array()
func craft_nodes():
var tally : MajorityJudgmentPollTally = get_poll().tally()
var candidate_index = 0 # as they were initially written, before the sort
for candidate in get_poll().get_candidates():
# var candidate_tally := tally.get_tally_of_candidate(candidate)
# var merit_profile = candidate_tally.merit_profile
var profile = create_merit_profile_scene(6)
if tally:
profile.refresh(tally.get_tally_of_candidate(candidate).merit_profile)
var height = profile.compute_height()
# profile.margin_top = candidate_index * 50
# profile.margin_left = 0
# profile.margin_right = 0
# profile.margin_bottom = candidate_index * 50 + 42
profile.rect_min_size = Vector2(400, height)
var container = $CenterContainer/ProfilesContainer
var wrapper = HBoxContainer.new()
var candidate_label = Label.new()
candidate_label.text = candidate.get_name()
candidate_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
# candidate_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
candidate_label.rect_min_size = Vector2(70, height)
candidate_label.valign = Label.VALIGN_CENTER
candidate_label.align = Label.ALIGN_RIGHT
wrapper.add_child(candidate_label)
wrapper.add_child(profile)
container.add_child(wrapper)
profiles_nodes.append(profile)
candidate_index += 1
func update_nodes():
var tally : MajorityJudgmentPollTally = get_poll().tally()
var candidate_index = 0 # as they were initially written, before the sort
for candidate in get_poll().get_candidates():
# var candidate_tally := tally.get_tally_of_candidate(candidate)
# var merit_profile = candidate_tally.merit_profile
var profile = profiles_nodes[candidate_index]
if tally:
var merit = tally.get_tally_of_candidate(candidate).merit_profile
profile.refresh(merit)
else:
var merit = poll.get_dummy_merit_profile(candidate)
profile.refresh(merit)
candidate_index += 1
#func test():
# var poll : MajorityJudgmentPoll = MajorityJudgmentPoll.make(
# "Test",
# MajorityJudgmentGrading.make_quality_6(),
# [
# MajorityJudgmentCandidate.make("Candidate A"),
# MajorityJudgmentCandidate.make("Candidate B"),
# MajorityJudgmentCandidate.make("Candidate C"),
# ]
# )
#
# var poll_tally = MajorityJudgmentPollTally.new()
# poll_tally.poll = poll
# var candidate_tallies = Array()
# var candidate_tally := MajorityJudgmentCandidateTally.new()
# candidate_tally.poll = poll
# candidate_tally.merit_profile = MajorityJudgmentCandidateMeritProfile.new()
# candidate_tally.merit_profile.grades = [
# 10,
# 2,
# 5,
# 7,
# 1,
# 11,
# ]
# candidate_tally.merit_profile.candidate_tally = candidate_tally
# candidate_tallies.append(candidate_tally)
# create_merit_profile_scene(candidate_tally.merit_profile)
#
## candidate_tally = MajorityJudgmentCandidateTally.new()
## candidate_tally.merit_profile = MajorityJudgmentCandidateMeritProfile.new()
## candidate_tally.merit_profile.grades = [
## 2,
## 7,
## 10,
## 11,
## 1,
## 5,
## ]
## candidate_tallies.append(candidate_tally)
## create_merit_profile_scene(candidate_tally.merit_profile)
#
# poll_tally.candidates_tallies = candidate_tallies
func create_merit_profile_scene(gradation_size:int):
var mps = MeritProfileScene.instance()
mps.set_poll(get_poll())
mps.craft_nodes(gradation_size)
return mps

78
addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.svg

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 16 16"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
inkscape:export-filename="/home/djrm/Projects/godot/tools/editor/icons/icon_node.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
sodipodi:docname="icon_node.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="4.1094292"
inkscape:cy="4.5020156"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3336"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1036.3622)">
<path
style="opacity:1;fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 8 2 A 6 6 0 0 0 2 8 A 6 6 0 0 0 8 14 A 6 6 0 0 0 14 8 A 6 6 0 0 0 8 2 z M 8 4 A 4 4 0 0 1 12 8 A 4 4 0 0 1 8 12 A 4 4 0 0 1 4 8 A 4 4 0 0 1 8 4 z "
transform="translate(0,1036.3622)"
id="path4154" />
</g>
</svg>

34
addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.svg.import

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/MajorityJudgmentLinearResultsControl.svg-fa6c8a91c72c88a932e738e8ceee0654.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.svg"
dest_files=[ "res://.import/MajorityJudgmentLinearResultsControl.svg-fa6c8a91c72c88a932e738e8ceee0654.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

22
addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.tscn

@ -0,0 +1,22 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.gd" type="Script" id=1]
[node name="MajorityJudgmentLinearResultsControl" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CenterContainer" type="CenterContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
[node name="ProfilesContainer" type="VBoxContainer" parent="CenterContainer"]
margin_left = 312.0
margin_top = 99.0
margin_right = 712.0
margin_bottom = 499.0
rect_min_size = Vector2( 400, 400 )

113
addons/majority_judgment/nodes/MajorityJudgmentMeritProfileControl.gd

@ -0,0 +1,113 @@
extends Control
const GradeNode = preload("res://addons/majority_judgment/nodes/MajorityJudgmentLinearGradeNode.tscn")
export(Resource) var poll setget set_poll, get_poll # MajorityJudgmentPoll
#export(Resource) var merit_profile # MajorityJudgmentCandidateMeritProfile
export var gap_size := 4 # pixels
export var total_width := 300 # pixels
var _grades_nodes := Array()
#func _ready():
# assert(self.merit_profile, "Inject a merit profile first")
# craft_nodes()
# refresh()
func set_poll(__poll:MajorityJudgmentPoll) -> void:
poll = __poll
func get_poll() -> MajorityJudgmentPoll:
return poll
func craft_nodes(grading_size):
assert(0 == _grades_nodes.size())
# or queue_free() them
for grade_index in range(grading_size):
var grade_node = GradeNode.instance()
grade_node.name = "GradeNode%d" % [grade_index]
grade_node.color = get_poll().get_grading().get_color_of_grade(grade_index)
add_child(grade_node)
_grades_nodes.append(grade_node)
func compute_height():
var gold = 2.0 / (sqrt(5.0) + 1.0)
assert(1.0/gold == 1.0+gold)
var amount_of_grades : int = poll.grading.grades.size()
return int(round(total_width / float(amount_of_grades)) * gold)
func refresh(merit_profile:MajorityJudgmentCandidateMeritProfile):
$Tween.remove_all()
var total_amount_of_judgments : int = merit_profile.count_judgments()
var amount_of_grades_with_judgments : int = merit_profile.count_grades_with_judgments()
var amount_of_grades : int = merit_profile.grades.size()
var height : int = compute_height()
var cursor := 0
for grade_index in range(amount_of_grades):
var grade_node = _grades_nodes[grade_index]
var amount_of_judgments_for_grade = merit_profile.grades[grade_index]
var width = (
(total_width - (amount_of_grades_with_judgments-1) * gap_size)
*
amount_of_judgments_for_grade
/
total_amount_of_judgments
)
var grade_margin_right = stepify(cursor + width, total_width / total_amount_of_judgments)
if 0 == width:
grade_margin_right = cursor
var tween_transition = Tween.TRANS_SINE
var tween_ease = Tween.EASE_IN_OUT
var tween_duration = 0.9
var tween_delay = 0
$Tween.interpolate_property(
grade_node,
"margin_left",
grade_node.margin_left,
cursor,
tween_duration,
tween_transition,
tween_ease,
tween_delay
)
$Tween.interpolate_property(
grade_node,
"margin_right",
grade_node.margin_right,
# cursor + width,
grade_margin_right,
tween_duration,
tween_transition,
tween_ease,
tween_delay
)
$Tween.start()
grade_node.margin_top = 0
grade_node.margin_bottom = height
# grade_node.margin_left = cursor
# grade_node.margin_right = cursor + width
if width:
# cursor = width + gap_size
cursor = grade_margin_right + gap_size

13
addons/majority_judgment/nodes/MajorityJudgmentMeritProfileControl.tscn

@ -0,0 +1,13 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/majority_judgment/nodes/MajorityJudgmentMeritProfileControl.gd" type="Script" id=1]
[node name="MajorityJudgmentMeritProfileControl" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Tween" type="Tween" parent="."]

3
core/App.gd

@ -39,5 +39,8 @@ func start_poll(poll):
# Start listening to chat commands
# Prepare timer for automatic closing of the poll
# Move to poll results scene
var _done = get_tree().change_scene("res://addons/majority_judgment/nodes/MajorityJudgmentLinearResultsControl.tscn")
yield(get_tree(), "idle_frame")
get_tree().current_scene.set_poll(poll)

7
gui/forms/NewPollForm.gd

@ -18,7 +18,12 @@ func collect_poll_from_form(form:Control) -> MajorityJudgmentPoll:
if title_line_edit.text:
poll.set_title(title_line_edit.text)
# var candidates_tree = form.find_node("CandidatesTree", true, false)
var candidates_tree = form.find_node("CandidatesTree", true, false)
var candidate_item = candidates_tree.get_root().get_children()
while (candidate_item):
poll.add_candidate(MajorityJudgmentCandidate.make(candidate_item.get_text(0)))
candidate_item = candidate_item.get_next()
poll.set_grading(MajorityJudgmentGrading.make_quality_6())
return poll
Loading…
Cancel
Save