diff --git a/gui/MainMenu.gd b/gui/MainMenu.gd new file mode 100644 index 0000000..eb5a3aa --- /dev/null +++ b/gui/MainMenu.gd @@ -0,0 +1,11 @@ +extends Control + + +func _on_NewPollButton_pressed(): + var changed = get_tree().change_scene("res://gui/forms/NewPollForm.tscn") + if OK != changed: + printerr("Failed to open the new poll form scene.") + + +func _on_ExitButton_pressed(): + get_tree().quit() diff --git a/gui/MainMenu.tscn b/gui/MainMenu.tscn new file mode 100644 index 0000000..5c8695f --- /dev/null +++ b/gui/MainMenu.tscn @@ -0,0 +1,54 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://gui/MainMenu.gd" type="Script" id=1] +[ext_resource path="res://gui/widgets/Background.tscn" type="PackedScene" id=2] + +[node name="Application" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Background" parent="." instance=ExtResource( 2 )] + +[node name="CenterContainer" type="CenterContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_lock_": true, +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] +margin_left = 478.0 +margin_top = 257.0 +margin_right = 546.0 +margin_bottom = 343.0 + +[node name="NewPollButton" type="Button" parent="CenterContainer/VBoxContainer"] +margin_right = 68.0 +margin_bottom = 20.0 +text = "New Poll" + +[node name="SettingsButton" type="Button" parent="CenterContainer/VBoxContainer"] +margin_top = 24.0 +margin_right = 68.0 +margin_bottom = 44.0 +text = "Settings" + +[node name="ExitButton" type="Button" parent="CenterContainer/VBoxContainer"] +margin_top = 48.0 +margin_right = 68.0 +margin_bottom = 68.0 +text = "Exit" + +[node name="VersionLabel" type="Label" parent="CenterContainer/VBoxContainer"] +margin_top = 72.0 +margin_right = 68.0 +margin_bottom = 86.0 +text = "v0.1" +align = 1 +[connection signal="pressed" from="CenterContainer/VBoxContainer/NewPollButton" to="." method="_on_NewPollButton_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/ExitButton" to="." method="_on_ExitButton_pressed"] diff --git a/gui/forms/CandidatesTree.gd b/gui/forms/CandidatesTree.gd new file mode 100644 index 0000000..85fd503 --- /dev/null +++ b/gui/forms/CandidatesTree.gd @@ -0,0 +1,131 @@ +extends Tree + + +const TEXT_COLUMN := 0 + + +# section == 0 if dropping on item +# section == -1 if dropping above item +# section == +1 if dropping below item +signal item_moved(item, to_item, section) + + +func _ready(): + setup() + setup_dummy_items() + append_item_invitation() + + +func setup(): + var root := create_item() + root.set_text(TEXT_COLUMN, "Candidates") + + connect("item_edited", self, "__on_item_edited") + connect("item_moved", self, "__on_item_moved") + + +func setup_dummy_items(): + create_candidate_item("Candidate A") + create_candidate_item("Candidate B") + + +func create_candidate_item(__name:String): + var candidate_item := create_item() + candidate_item.set_editable(TEXT_COLUMN, true) + candidate_item.set_text(TEXT_COLUMN, __name) + + +func append_item_invitation(): + var invite_text = "+" + var root = get_root() +# var item = root.get_children() # get_first_child(), probably + var items = __get_children_items(root) + + if items and items.back().get_text(TEXT_COLUMN) == invite_text: + return + + var invite = create_item(root) + invite.set_editable(TEXT_COLUMN, true) + invite.set_text(TEXT_COLUMN, invite_text) + invite.move_to_bottom() # superfluous + +func prune_empty_items(): + var items_to_prune = Array() + for item in __get_children_items(get_root()): + if item.get_text(TEXT_COLUMN) == '': + if item.get_next(): + item.get_next().select(TEXT_COLUMN) + ensure_cursor_is_visible() + item.free() + + +func __on_item_edited(): + append_item_invitation() + + var selected = get_next_selected(null) + if selected and selected.get_next(): + selected.get_next().select(TEXT_COLUMN) + ensure_cursor_is_visible() + + prune_empty_items() # after, since it may change item selection + + +func __on_item_moved(item, to_item, section): + if not to_item: + printerr("No item unto which to drop. Weird.") + var offset = 0 if section <= 0 else 1 + var new_item = create_item(get_root(), __get_item_index(to_item)+offset) + new_item.set_text(TEXT_COLUMN, item.get_text(TEXT_COLUMN)) + new_item.set_editable(TEXT_COLUMN, true) + new_item.select(TEXT_COLUMN) + item.free() + ensure_cursor_is_visible() + + +func __get_children_items(parent:TreeItem) -> Array: + var items := Array() + var item = parent.get_children() + while (item): + items.append(item) + item = item.get_next() + return items + + +func __get_item_index(of_item:TreeItem): + var parent = of_item.get_parent() + assert(parent) # or return … what, zero? + var index := 0 + var item : TreeItem = parent.get_children() + while (item): + if item == of_item: + return index + item = item.get_next() + index += 1 + assert(false, "Item not found") + return -1 + + + +func get_drag_data(_position) -> TreeItem: + set_drop_mode_flags(DROP_MODE_INBETWEEN) + + var item_preview = Label.new() + item_preview.text = get_selected().get_text(TEXT_COLUMN) + set_drag_preview(item_preview) + + return get_selected() + + +func can_drop_data(_position, data): + return data is TreeItem + + +func drop_data(position, item): + var to_item = get_item_at_position(position) + var section = get_drop_section_at_position(position) + if not to_item or section == -100: + #printerr("Dropping in the void") + to_item = get_root().get_children() + section = 0 + + emit_signal('item_moved', item, to_item, section) diff --git a/gui/forms/NewPollForm.gd b/gui/forms/NewPollForm.gd new file mode 100644 index 0000000..d22fd14 --- /dev/null +++ b/gui/forms/NewPollForm.gd @@ -0,0 +1,24 @@ +extends Control + + +func _on_CancelButton_pressed(): + get_tree().change_scene("res://gui/MainMenu.tscn") + + +func _on_LaunchButton_pressed(): + var poll = collect_poll_from_form(self) + if poll: + App.start_poll(poll) + + +func collect_poll_from_form(form:Control) -> MajorityJudgmentPoll: + var poll = MajorityJudgmentPoll.new() + + var title_line_edit = form.find_node("TitleLineEdit", true, false) + if title_line_edit.text: + poll.set_title(title_line_edit.text) + + var candidates_tree = form.find_node("CandidatesTree", true, false) + + + return poll diff --git a/gui/forms/NewPollForm.tscn b/gui/forms/NewPollForm.tscn new file mode 100644 index 0000000..d9f0fd4 --- /dev/null +++ b/gui/forms/NewPollForm.tscn @@ -0,0 +1,128 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://gui/widgets/Background.tscn" type="PackedScene" id=1] +[ext_resource path="res://gui/forms/NewPollForm.gd" type="Script" id=2] +[ext_resource path="res://gui/forms/CandidatesTree.gd" type="Script" id=3] + +[node name="NewPollForm" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Background" parent="." instance=ExtResource( 1 )] + +[node name="CenterContainer" type="CenterContainer" parent="."] +anchor_top = -0.00187078 +anchor_right = 1.0 +anchor_bottom = 0.998129 +__meta__ = { +"_edit_lock_": true, +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] +margin_left = 397.0 +margin_top = 179.0 +margin_right = 627.0 +margin_bottom = 419.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleHBoxContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +margin_right = 230.0 +margin_bottom = 24.0 + +[node name="Label" type="Label" parent="CenterContainer/VBoxContainer/TitleHBoxContainer"] +margin_top = 5.0 +margin_right = 28.0 +margin_bottom = 19.0 +text = "Title" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleLineEdit" type="LineEdit" parent="CenterContainer/VBoxContainer/TitleHBoxContainer"] +margin_left = 32.0 +margin_right = 230.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +max_length = 256 +placeholder_text = "Who should we raid?" + +[node name="CandidatesContainer" type="MarginContainer" parent="CenterContainer/VBoxContainer"] +margin_top = 28.0 +margin_right = 230.0 +margin_bottom = 148.0 +rect_min_size = Vector2( 230, 120 ) +size_flags_vertical = 3 + +[node name="CandidatesTree" type="Tree" parent="CenterContainer/VBoxContainer/CandidatesContainer"] +margin_right = 230.0 +margin_bottom = 120.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +hide_folding = true +hide_root = true +script = ExtResource( 3 ) + +[node name="GradingHBoxContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +margin_top = 152.0 +margin_right = 230.0 +margin_bottom = 172.0 + +[node name="Label" type="Label" parent="CenterContainer/VBoxContainer/GradingHBoxContainer"] +self_modulate = Color( 1, 1, 1, 0.25098 ) +margin_top = 3.0 +margin_right = 49.0 +margin_bottom = 17.0 +text = "Grading" + +[node name="GradingMenuButton" type="MenuButton" parent="CenterContainer/VBoxContainer/GradingHBoxContainer"] +margin_left = 53.0 +margin_right = 230.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +disabled = true +text = "Quality (6 grades)" +flat = false +clip_text = true +items = [ "Quality (2 grades)", null, 2, true, false, 0, 0, null, "", false, "Quality (3 grades)", null, 2, false, false, 0, 0, null, "", false, "Quality (4 grades)", null, 2, false, false, 2, 0, null, "", false ] + +[node name="DelegationCheckButton" type="CheckButton" parent="CenterContainer/VBoxContainer"] +margin_top = 176.0 +margin_right = 230.0 +margin_bottom = 216.0 +disabled = true +text = "Allow Delegations" + +[node name="ButtonsHBoxContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +margin_top = 220.0 +margin_right = 230.0 +margin_bottom = 240.0 + +[node name="CancelButton" type="Button" parent="CenterContainer/VBoxContainer/ButtonsHBoxContainer"] +margin_right = 109.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +text = "CANCEL" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VSeparator" type="VSeparator" parent="CenterContainer/VBoxContainer/ButtonsHBoxContainer"] +margin_left = 113.0 +margin_right = 117.0 +margin_bottom = 20.0 + +[node name="LaunchButton" type="Button" parent="CenterContainer/VBoxContainer/ButtonsHBoxContainer"] +margin_left = 121.0 +margin_right = 230.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +text = "CREATE" +[connection signal="pressed" from="CenterContainer/VBoxContainer/ButtonsHBoxContainer/CancelButton" to="." method="_on_CancelButton_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/ButtonsHBoxContainer/LaunchButton" to="." method="_on_LaunchButton_pressed"] diff --git a/gui/forms/twitch_config/TwitchAuthConfig.gd b/gui/forms/twitch_config/TwitchAuthConfig.gd new file mode 100644 index 0000000..fcfee27 --- /dev/null +++ b/gui/forms/twitch_config/TwitchAuthConfig.gd @@ -0,0 +1,116 @@ +extends Control + + +# Allow streamers to define their twitch credentials. +# Since this config is sensitive, should it be encrypted? +# +# Generate application +# https://dev.twitch.tv/console/apps/create +# +# Get OAuth Token +# https://twitchapps.com/tmi/ + + +var config := { + 'nick': '', # whatever you want + 'channel': '', # twitch identifier, usually lowercase + 'client': '', # Identifies the application registered on twitch + 'oauth': '', # https://twitchapps.com/tmi/ +} + + +const TWITCH_OAUTH_FILE_PATH = "user://twitch_chat_auth_config.json" + + +onready var OAuthEdit = find_node("OAuthLineEdit") +onready var NickEdit = find_node("NickLineEdit") +onready var ClientIdEdit = find_node("ClientIdLineEdit") +onready var ChannelEdit = find_node("ChannelLineEdit") + + +func _ready(): + read_from_file() + write_to_ui() + + +# _____ _ _ +# | ___(_) | ___ +# | |_ | | |/ _ \ +# | _| | | | __/ +# |_| |_|_|\___| +# + + +func read_from_file(): + var file = File.new() + if not file.file_exists(TWITCH_OAUTH_FILE_PATH): + return Dictionary() + + file.open(TWITCH_OAUTH_FILE_PATH, File.READ) + + var json = file.get_as_text() + var dson = JSON.parse(json) + + if dson.error: + printerr("Auth config file exists but is confusing me.") + return null + + self.config = dson.result + file.close() + + return self.config + + +func write_to_file(): + var file = File.new() + file.open(TWITCH_OAUTH_FILE_PATH, File.WRITE) + file.store_string(JSON.print(self.config, ' ')) + file.close() + + +# _ _ ___ +# | | | |_ _| +# | | | || | +# | |_| || | +# \___/|___| +# + + +func read_from_ui(): + self.config['oauth'] = OAuthEdit.text + self.config['nick'] = NickEdit.text + self.config['channel'] = ChannelEdit.text + self.config['client'] = ClientIdEdit.text + + +func write_to_ui(): + OAuthEdit.text = self.config['oauth'] + NickEdit.text = self.config['nick'] + ChannelEdit.text = self.config['channel'] + ClientIdEdit.text = self.config['client'] + + +# _____ _ +# | ____|_ _____ _ __ | |_ ___ +# | _| \ \ / / _ \ '_ \| __/ __| +# | |___ \ V / __/ | | | |_\__ \ +# |_____| \_/ \___|_| |_|\__|___/ +# + + +func _on_SaveButton_pressed(): + read_from_ui() + write_to_file() + App.go_to_main_menu() + + +func _on_CancelButton_pressed(): + App.go_to_main_menu() + + +func _on_ClientIdHelpButton_pressed(): + OS.shell_open("https://dev.twitch.tv/dashboard/apps/create") + + +func _on_OAuthHelpButton_pressed(): + OS.shell_open("https://twitchapps.com/tmi/") diff --git a/gui/forms/twitch_config/TwitchAuthConfig.tscn b/gui/forms/twitch_config/TwitchAuthConfig.tscn new file mode 100644 index 0000000..cb0dfce --- /dev/null +++ b/gui/forms/twitch_config/TwitchAuthConfig.tscn @@ -0,0 +1,158 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://gui/forms/twitch_config/TwitchAuthConfig.gd" type="Script" id=1] +[ext_resource path="res://gui/widgets/Background.tscn" type="PackedScene" id=2] + +[node name="TwitchAuthConfig" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Background" parent="." instance=ExtResource( 2 )] + +[node name="CenterContainer" type="CenterContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_top = -1.05945 +margin_bottom = -1.05945 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] +margin_left = 410.0 +margin_top = 221.0 +margin_right = 613.0 +margin_bottom = 379.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TitleLabel" type="Label" parent="CenterContainer/VBoxContainer"] +margin_right = 203.0 +margin_bottom = 14.0 +text = "TWITCH CHAT CONFIGURATION" + +[node name="NickContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +margin_top = 18.0 +margin_right = 203.0 +margin_bottom = 42.0 +size_flags_horizontal = 3 + +[node name="NickLabel" type="Label" parent="CenterContainer/VBoxContainer/NickContainer"] +margin_top = 5.0 +margin_right = 28.0 +margin_bottom = 19.0 +text = "Nick" + +[node name="NickLineEdit" type="LineEdit" parent="CenterContainer/VBoxContainer/NickContainer"] +margin_left = 32.0 +margin_right = 203.0 +margin_bottom = 24.0 +hint_tooltip = "May be whatever you want." +size_flags_horizontal = 3 + +[node name="ChannelContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +margin_top = 46.0 +margin_right = 203.0 +margin_bottom = 70.0 +size_flags_horizontal = 3 + +[node name="ChannelLabel" type="Label" parent="CenterContainer/VBoxContainer/ChannelContainer"] +margin_top = 5.0 +margin_right = 51.0 +margin_bottom = 19.0 +text = "Channel" + +[node name="ChannelLineEdit" type="LineEdit" parent="CenterContainer/VBoxContainer/ChannelContainer"] +margin_left = 55.0 +margin_right = 203.0 +margin_bottom = 24.0 +hint_tooltip = "Your twitch username, usually. Usually lowercase." +size_flags_horizontal = 3 + +[node name="ClientIdContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +margin_top = 74.0 +margin_right = 203.0 +margin_bottom = 98.0 +size_flags_horizontal = 3 + +[node name="ClientIdLabel" type="Label" parent="CenterContainer/VBoxContainer/ClientIdContainer"] +margin_top = 5.0 +margin_right = 54.0 +margin_bottom = 19.0 +text = "Client Id" + +[node name="ClientIdLineEdit" type="LineEdit" parent="CenterContainer/VBoxContainer/ClientIdContainer"] +margin_left = 58.0 +margin_right = 181.0 +margin_bottom = 24.0 +hint_tooltip = "May be generated here : https://dev.twitch.tv/dashboard/apps/create" +size_flags_horizontal = 3 + +[node name="ClientIdHelpButton" type="Button" parent="CenterContainer/VBoxContainer/ClientIdContainer"] +margin_left = 185.0 +margin_right = 203.0 +margin_bottom = 24.0 +text = "?" + +[node name="OAuthContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +margin_top = 102.0 +margin_right = 203.0 +margin_bottom = 126.0 +size_flags_horizontal = 3 + +[node name="OAuthLabel" type="Label" parent="CenterContainer/VBoxContainer/OAuthContainer"] +margin_top = 5.0 +margin_right = 83.0 +margin_bottom = 19.0 +text = "OAuth Token" + +[node name="OAuthLineEdit" type="LineEdit" parent="CenterContainer/VBoxContainer/OAuthContainer"] +margin_left = 87.0 +margin_right = 181.0 +margin_bottom = 24.0 +hint_tooltip = "May be generated here : https://twitchapps.com/tmi/" +size_flags_horizontal = 3 +secret = true + +[node name="OAuthHelpButton" type="Button" parent="CenterContainer/VBoxContainer/OAuthContainer"] +margin_left = 185.0 +margin_right = 203.0 +margin_bottom = 24.0 +text = "?" + +[node name="HSeparator" type="HSeparator" parent="CenterContainer/VBoxContainer"] +margin_top = 130.0 +margin_right = 203.0 +margin_bottom = 134.0 +rect_min_size = Vector2( 200, 0 ) + +[node name="ButtonsContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +margin_top = 138.0 +margin_right = 203.0 +margin_bottom = 158.0 + +[node name="CancelButton" type="Button" parent="CenterContainer/VBoxContainer/ButtonsContainer"] +margin_right = 54.0 +margin_bottom = 20.0 +text = "Cancel" + +[node name="VSeparator" type="VSeparator" parent="CenterContainer/VBoxContainer/ButtonsContainer"] +margin_left = 58.0 +margin_right = 158.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 + +[node name="SaveButton" type="Button" parent="CenterContainer/VBoxContainer/ButtonsContainer"] +margin_left = 162.0 +margin_right = 203.0 +margin_bottom = 20.0 +text = "Save" +[connection signal="pressed" from="CenterContainer/VBoxContainer/ClientIdContainer/ClientIdHelpButton" to="." method="_on_ClientIdHelpButton_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/OAuthContainer/OAuthHelpButton" to="." method="_on_OAuthHelpButton_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/ButtonsContainer/CancelButton" to="." method="_on_CancelButton_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/ButtonsContainer/SaveButton" to="." method="_on_SaveButton_pressed"] diff --git a/gui/widgets/Background.tscn b/gui/widgets/Background.tscn new file mode 100644 index 0000000..374e37a --- /dev/null +++ b/gui/widgets/Background.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://sprites/mieuxvoter_logo.png" type="Texture" id=1] + +[node name="Background" type="Panel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TextureRect" type="TextureRect" parent="."] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -150.193 +margin_top = -88.2292 +margin_right = -0.193359 +margin_bottom = -0.229187 +texture = ExtResource( 1 )