initial commit

This commit is contained in:
mr0xb 2025-12-27 00:20:33 -05:00
commit 94847a50a2
42 changed files with 2063 additions and 0 deletions

View file

@ -0,0 +1,106 @@
extends Panel
signal deck_saved(deck: Deck)
signal editor_closed()
var current_deck: Deck
@onready var deck_name_edit = $MarginContainer/VBoxContainer/DeckInfoSection/DeckNameEdit
@onready var deck_desc_edit = $MarginContainer/VBoxContainer/DeckInfoSection/DeckDescEdit
@onready var color_picker = $MarginContainer/VBoxContainer/DeckInfoSection/ColorPicker
@onready var cards_list = $MarginContainer/VBoxContainer/CardsSection/ScrollContainer/CardsList
@onready var save_btn = $MarginContainer/VBoxContainer/ButtonsSection/SaveButton
@onready var cancel_btn = $MarginContainer/VBoxContainer/ButtonsSection/CancelButton
@onready var add_card_btn = $MarginContainer/VBoxContainer/CardsSection/AddCardButton
func _ready() -> void:
save_btn.pressed.connect(_on_save_pressed)
cancel_btn.pressed.connect(_on_cancel_pressed)
add_card_btn.pressed.connect(_on_add_card_pressed)
hide()
func edit_deck(deck: Deck) -> void:
current_deck = deck
deck_name_edit.text = deck.name
deck_desc_edit.text = deck.description
color_picker.color = deck.color_theme
_refresh_cards_list()
show()
func _refresh_cards_list() -> void:
for child in cards_list.get_children():
child.queue_free()
for i in range(current_deck.cards.size()):
var card = current_deck.cards[i]
_add_card_ui_entry(card, i)
func _add_card_ui_entry(card: Flashcard, index: int) -> void:
var card_panel = PanelContainer.new()
var margin = MarginContainer.new()
margin.add_theme_constant_override("margin_left", 10)
margin.add_theme_constant_override("margin_top", 10)
margin.add_theme_constant_override("margin_right", 10)
margin.add_theme_constant_override("margin_bottom", 10)
card_panel.add_child(margin)
var vbox = VBoxContainer.new()
margin.add_child(vbox)
var q_hbox = HBoxContainer.new()
vbox.add_child(q_hbox)
var q_label = Label.new()
q_label.text = "Q: "
q_hbox.add_child(q_label)
var q_edit = LineEdit.new()
q_edit.text = card.question_text
q_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
q_edit.text_changed.connect(func(new_text): card.question_text = new_text)
q_hbox.add_child(q_edit)
var a_hbox = HBoxContainer.new()
vbox.add_child(a_hbox)
var a_label = Label.new()
a_label.text = "A: "
a_hbox.add_child(a_label)
var a_edit = LineEdit.new()
a_edit.text = card.answer_text
a_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
a_edit.text_changed.connect(func(new_text): card.answer_text = new_text)
a_hbox.add_child(a_edit)
var delete_btn = Button.new()
delete_btn.text = "Delete"
delete_btn.pressed.connect(func(): _on_delete_card_pressed(index))
vbox.add_child(delete_btn)
cards_list.add_child(card_panel)
func _on_add_card_pressed() -> void:
var new_card = Flashcard.new()
new_card.question_text = "New Question"
new_card.answer_text = "New Answer"
current_deck.add_card(new_card)
_refresh_cards_list()
func _on_delete_card_pressed(index: int) -> void:
if index >= 0 and index < current_deck.cards.size():
current_deck.cards.remove_at(index)
_refresh_cards_list()
func _on_save_pressed() -> void:
current_deck.name = deck_name_edit.text
current_deck.description = deck_desc_edit.text
current_deck.color_theme = color_picker.color
DataManager.save_deck(current_deck)
deck_saved.emit(current_deck)
hide()
func _on_cancel_pressed() -> void:
editor_closed.emit()
hide()

View file

@ -0,0 +1 @@
uid://caqx35wgjp2v0

View file

@ -0,0 +1,100 @@
[gd_scene load_steps=2 format=3 uid="uid://c4p1nfvj0oy7k"]
[ext_resource type="Script" path="res://scenes/ui/deck_manager/deck_editor.gd" id="1_b0nwp"]
[node name="DeckEditor" type="Panel"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_b0nwp")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 60
theme_override_constants/margin_top = 60
theme_override_constants/margin_right = 60
theme_override_constants/margin_bottom = 60
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="Title" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Edit Deck"
horizontal_alignment = 1
[node name="DeckInfoSection" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="NameLabel" type="Label" parent="MarginContainer/VBoxContainer/DeckInfoSection"]
layout_mode = 2
text = "Deck Name:"
[node name="DeckNameEdit" type="LineEdit" parent="MarginContainer/VBoxContainer/DeckInfoSection"]
layout_mode = 2
placeholder_text = "Enter deck name"
[node name="DescLabel" type="Label" parent="MarginContainer/VBoxContainer/DeckInfoSection"]
layout_mode = 2
text = "Description:"
[node name="DeckDescEdit" type="TextEdit" parent="MarginContainer/VBoxContainer/DeckInfoSection"]
custom_minimum_size = Vector2(0, 60)
layout_mode = 2
placeholder_text = "Enter deck description"
[node name="ColorLabel" type="Label" parent="MarginContainer/VBoxContainer/DeckInfoSection"]
layout_mode = 2
text = "Deck Color:"
[node name="ColorPicker" type="ColorPickerButton" parent="MarginContainer/VBoxContainer/DeckInfoSection"]
layout_mode = 2
[node name="CardsSection" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 10
[node name="CardsLabel" type="Label" parent="MarginContainer/VBoxContainer/CardsSection"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "Cards:"
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/CardsSection"]
layout_mode = 2
size_flags_vertical = 3
[node name="CardsList" type="VBoxContainer" parent="MarginContainer/VBoxContainer/CardsSection/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 10
[node name="AddCardButton" type="Button" parent="MarginContainer/VBoxContainer/CardsSection"]
custom_minimum_size = Vector2(0, 40)
layout_mode = 2
text = "+ Add Card"
[node name="ButtonsSection" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 20
alignment = 1
[node name="CancelButton" type="Button" parent="MarginContainer/VBoxContainer/ButtonsSection"]
custom_minimum_size = Vector2(150, 50)
layout_mode = 2
text = "Cancel"
[node name="SaveButton" type="Button" parent="MarginContainer/VBoxContainer/ButtonsSection"]
custom_minimum_size = Vector2(150, 50)
layout_mode = 2
text = "Save Deck"

View file

@ -0,0 +1,45 @@
extends PanelContainer
signal study_requested(deck: Deck)
signal edit_requested(deck: Deck)
signal delete_requested(deck: Deck)
var deck: Deck
@onready var deck_name_label = $MarginContainer/HBoxContainer/VBoxContainer/DeckName
@onready var deck_info_label = $MarginContainer/HBoxContainer/VBoxContainer/DeckInfo
@onready var color_indicator = $MarginContainer/HBoxContainer/ColorIndicator
@onready var study_btn = $MarginContainer/HBoxContainer/Actions/StudyButton
@onready var edit_btn = $MarginContainer/HBoxContainer/Actions/EditButton
@onready var delete_btn = $MarginContainer/HBoxContainer/Actions/DeleteButton
func _ready() -> void:
study_btn.pressed.connect(_on_study_pressed)
edit_btn.pressed.connect(_on_edit_pressed)
delete_btn.pressed.connect(_on_delete_pressed)
func set_deck(p_deck: Deck) -> void:
deck = p_deck
if deck:
deck_name_label.text = deck.name
deck_info_label.text = "%d cards" % deck.get_card_count()
color_indicator.color = deck.color_theme
var progress = DataManager.get_progress(deck.id)
if progress:
deck_info_label.text += "%.1f%% accuracy" % progress.get_accuracy()
func _on_study_pressed() -> void:
if deck and deck.get_card_count() > 0:
study_requested.emit(deck)
else:
push_warning("Cannot study empty deck")
func _on_edit_pressed() -> void:
if deck:
edit_requested.emit(deck)
func _on_delete_pressed() -> void:
if deck:
delete_requested.emit(deck)

View file

@ -0,0 +1 @@
uid://uu8j34y86hs3

View file

@ -0,0 +1,56 @@
[gd_scene load_steps=2 format=3 uid="uid://clrpqfuax7qhv"]
[ext_resource type="Script" path="res://scenes/ui/deck_manager/deck_list_item.gd" id="1_qp4wn"]
[node name="DeckListItem" type="PanelContainer"]
custom_minimum_size = Vector2(0, 80)
script = ExtResource("1_qp4wn")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="ColorIndicator" type="ColorRect" parent="MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(8, 0)
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 5
[node name="DeckName" type="Label" parent="MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "Deck Name"
[node name="DeckInfo" type="Label" parent="MarginContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0.7, 0.7, 0.7, 1)
theme_override_font_sizes/font_size = 14
text = "0 cards"
[node name="Actions" type="HBoxContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="StudyButton" type="Button" parent="MarginContainer/HBoxContainer/Actions"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "Study"
[node name="EditButton" type="Button" parent="MarginContainer/HBoxContainer/Actions"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "Edit"
[node name="DeleteButton" type="Button" parent="MarginContainer/HBoxContainer/Actions"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "Delete"

View file

@ -0,0 +1,89 @@
extends Control
const DeckListItemScene = preload("res://scenes/ui/deck_manager/deck_list_item.tscn")
@onready var back_btn = $MarginContainer/VBoxContainer/Header/BackButton
@onready var browse_online_btn = $MarginContainer/VBoxContainer/Header/BrowseOnlineButton
@onready var create_deck_btn = $MarginContainer/VBoxContainer/Header/CreateDeckButton
@onready var deck_list_container = $MarginContainer/VBoxContainer/ScrollContainer/DeckListContainer
@onready var modal_overlay = $ModalOverlay
@onready var deck_editor = $DeckEditor
@onready var online_browser = $OnlineDeckBrowser
func _ready() -> void:
back_btn.pressed.connect(_on_back_pressed)
browse_online_btn.pressed.connect(_on_browse_online_pressed)
create_deck_btn.pressed.connect(_on_create_deck_pressed)
DataManager.decks_loaded.connect(_refresh_deck_list)
DataManager.deck_saved.connect(_on_deck_saved)
DataManager.deck_deleted.connect(_on_deck_deleted)
deck_editor.deck_saved.connect(_on_editor_deck_saved)
deck_editor.editor_closed.connect(_on_editor_closed)
online_browser.browser_closed.connect(_on_browser_closed)
_refresh_deck_list()
func _refresh_deck_list() -> void:
for child in deck_list_container.get_children():
child.queue_free()
for deck in DataManager.decks:
var item = DeckListItemScene.instantiate()
deck_list_container.add_child(item)
item.set_deck(deck)
item.study_requested.connect(_on_study_deck)
item.edit_requested.connect(_on_edit_deck)
item.delete_requested.connect(_on_delete_deck)
print("Refreshed deck list with %d deck(s)" % DataManager.decks.size())
func _on_back_pressed() -> void:
GameManager.return_to_main_menu()
func _on_create_deck_pressed() -> void:
var new_deck = Deck.new()
new_deck.name = "New Deck"
new_deck.description = "A new flashcard deck"
var sample_card = Flashcard.new()
sample_card.question_text = "Sample Question"
sample_card.answer_text = "Sample Answer"
new_deck.add_card(sample_card)
DataManager.save_deck(new_deck)
func _on_study_deck(deck: Deck) -> void:
GameManager.start_quiz(deck)
func _on_edit_deck(deck: Deck) -> void:
modal_overlay.show()
deck_editor.edit_deck(deck)
func _on_delete_deck(deck: Deck) -> void:
print("Deleting deck: " + deck.name)
DataManager.delete_deck(deck.id)
func _on_deck_saved(deck: Deck) -> void:
_refresh_deck_list()
func _on_deck_deleted(deck_id: String) -> void:
_refresh_deck_list()
func _on_editor_deck_saved(deck: Deck) -> void:
_refresh_deck_list()
func _on_editor_closed() -> void:
modal_overlay.hide()
func _on_browse_online_pressed() -> void:
modal_overlay.show()
online_browser.open_browser()
func _on_browser_closed() -> void:
modal_overlay.hide()
# Refresh the deck list in case new decks were downloaded
_refresh_deck_list()

View file

@ -0,0 +1 @@
uid://becqmtpte7ysr

View file

@ -0,0 +1,84 @@
[gd_scene load_steps=4 format=3 uid="uid://bpd7dt0cpoh1m"]
[ext_resource type="Script" uid="uid://becqmtpte7ysr" path="res://scenes/ui/deck_manager/deck_manager.gd" id="1_jh7lm"]
[ext_resource type="PackedScene" path="res://scenes/ui/deck_manager/deck_editor.tscn" id="2_deck_editor"]
[ext_resource type="PackedScene" uid="uid://chj1kxack8ean" path="res://scenes/ui/deck_manager/online_deck_browser.tscn" id="3_online_browser"]
[node name="DeckManager" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_jh7lm")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 40
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 40
theme_override_constants/margin_bottom = 40
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="Header" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="BackButton" type="Button" parent="MarginContainer/VBoxContainer/Header"]
custom_minimum_size = Vector2(100, 50)
layout_mode = 2
text = "← Back"
[node name="Title" type="Label" parent="MarginContainer/VBoxContainer/Header"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 32
text = "Deck Manager"
horizontal_alignment = 1
vertical_alignment = 1
[node name="BrowseOnlineButton" type="Button" parent="MarginContainer/VBoxContainer/Header"]
custom_minimum_size = Vector2(150, 50)
layout_mode = 2
text = "🌐 Browse Online"
[node name="CreateDeckButton" type="Button" parent="MarginContainer/VBoxContainer/Header"]
custom_minimum_size = Vector2(150, 50)
layout_mode = 2
text = "+ New Deck"
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="DeckListContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 10
[node name="ModalOverlay" type="ColorRect" parent="."]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0.9411765)
[node name="DeckEditor" parent="." instance=ExtResource("2_deck_editor")]
visible = false
layout_mode = 1
[node name="OnlineDeckBrowser" parent="." instance=ExtResource("3_online_browser")]
visible = false
layout_mode = 1

View file

@ -0,0 +1,133 @@
extends Panel
signal browser_closed()
const API_BASE_URL = "http://localhost:8080"
@onready var close_btn = $MarginContainer/VBoxContainer/Header/CloseButton
@onready var refresh_btn = $MarginContainer/VBoxContainer/Header/RefreshButton
@onready var status_label = $MarginContainer/VBoxContainer/StatusLabel
@onready var decks_container = $MarginContainer/VBoxContainer/ScrollContainer/DecksContainer
var http_request: HTTPRequest
func _ready() -> void:
close_btn.pressed.connect(_on_close_pressed)
refresh_btn.pressed.connect(_on_refresh_pressed)
hide()
func open_browser() -> void:
show()
_load_online_decks()
func _load_online_decks() -> void:
status_label.text = "Loading decks from API..."
_clear_deck_list()
if http_request:
http_request.queue_free()
http_request = HTTPRequest.new()
add_child(http_request)
http_request.request_completed.connect(_on_decks_loaded)
var error = http_request.request(API_BASE_URL + "/decks")
if error != OK:
status_label.text = "Error: Failed to connect to API server"
print("HTTP request error: ", error)
func _on_decks_loaded(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS:
status_label.text = "Error: Failed to load decks"
return
if response_code != 200:
status_label.text = "Error: API returned code " + str(response_code)
return
var json_string = body.get_string_from_utf8()
var json = JSON.new()
var parse_error = json.parse(json_string)
if parse_error != OK:
status_label.text = "Error: Failed to parse response"
return
var decks_list = json.data
if decks_list.is_empty():
status_label.text = "No decks available on server"
return
status_label.text = "Found %d deck(s) online" % decks_list.size()
_display_decks(decks_list)
func _display_decks(decks_list: Array) -> void:
_clear_deck_list()
for deck_data in decks_list:
var deck_info = deck_data.get("deck", {})
var metadata = deck_info.get("metadata", {})
var author_info = deck_info.get("author", {})
var deck_item = PanelContainer.new()
var margin = MarginContainer.new()
margin.add_theme_constant_override("margin_left", 15)
margin.add_theme_constant_override("margin_top", 15)
margin.add_theme_constant_override("margin_right", 15)
margin.add_theme_constant_override("margin_bottom", 15)
deck_item.add_child(margin)
var vbox = VBoxContainer.new()
margin.add_child(vbox)
var name_label = Label.new()
name_label.text = deck_info.get("name", "Unknown Deck")
name_label.add_theme_font_size_override("font_size", 20)
vbox.add_child(name_label)
var desc_label = Label.new()
desc_label.text = deck_info.get("description", "")
desc_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
vbox.add_child(desc_label)
var cards_count = deck_info.get("cards", []).size()
var rating = metadata.get("rating", 0.0)
var downloads = metadata.get("download_count", 0)
var author_name = author_info.get("name", "N/A")
var meta_label = Label.new()
meta_label.text = "%d cards • ⭐ %.1f%d downloads 🧑 %s" % [cards_count, rating, downloads, author_name]
meta_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6))
meta_label.add_theme_font_size_override("font_size", 14)
vbox.add_child(meta_label)
var download_btn = Button.new()
download_btn.text = "Download Deck"
download_btn.custom_minimum_size = Vector2(150, 40)
download_btn.pressed.connect(_on_download_deck.bind(deck_info.get("id", "")))
vbox.add_child(download_btn)
decks_container.add_child(deck_item)
func _on_download_deck(deck_id: String) -> void:
if deck_id == "":
return
status_label.text = "Downloading deck..."
print("Downloading deck: " + deck_id)
DataManager.download_deck_from_api(API_BASE_URL + "/decks/" + deck_id)
await get_tree().create_timer(1.0).timeout
status_label.text = "Deck downloaded! Check your deck list."
func _clear_deck_list() -> void:
for child in decks_container.get_children():
child.queue_free()
func _on_refresh_pressed() -> void:
_load_online_decks()
func _on_close_pressed() -> void:
browser_closed.emit()
hide()

View file

@ -0,0 +1 @@
uid://b1tcvxtd8urjl

View file

@ -0,0 +1,62 @@
[gd_scene load_steps=2 format=3 uid="uid://chj1kxack8ean"]
[ext_resource type="Script" uid="uid://b1tcvxtd8urjl" path="res://scenes/ui/deck_manager/online_deck_browser.gd" id="1_online"]
[node name="OnlineDeckBrowser" type="Panel"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_online")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 60
theme_override_constants/margin_top = 60
theme_override_constants/margin_right = 60
theme_override_constants/margin_bottom = 60
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="Header" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="MarginContainer/VBoxContainer/Header"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 32
text = "Browse Online Decks"
horizontal_alignment = 1
[node name="RefreshButton" type="Button" parent="MarginContainer/VBoxContainer/Header"]
custom_minimum_size = Vector2(120, 50)
layout_mode = 2
text = "🔄 Refresh"
[node name="CloseButton" type="Button" parent="MarginContainer/VBoxContainer/Header"]
custom_minimum_size = Vector2(100, 50)
layout_mode = 2
text = "Close"
[node name="StatusLabel" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "Loading..."
horizontal_alignment = 1
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="DecksContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 15

View file

@ -0,0 +1,19 @@
extends Control
@onready var manage_decks_btn = $MarginContainer/VBoxContainer/MenuButtons/ManageDecksButton
@onready var view_progress_btn = $MarginContainer/VBoxContainer/MenuButtons/ViewProgressButton
@onready var quit_btn = $MarginContainer/VBoxContainer/MenuButtons/QuitButton
func _ready() -> void:
manage_decks_btn.pressed.connect(_on_manage_decks_pressed)
view_progress_btn.pressed.connect(_on_view_progress_pressed)
quit_btn.pressed.connect(_on_quit_pressed)
func _on_manage_decks_pressed() -> void:
GameManager.open_deck_manager()
func _on_view_progress_pressed() -> void:
print("View progress - not implemented yet")
func _on_quit_pressed() -> void:
get_tree().quit()

View file

@ -0,0 +1 @@
uid://u6qhaped7c3i

View file

@ -0,0 +1,60 @@
[gd_scene load_steps=2 format=3 uid="uid://c0ka24qxbge17"]
[ext_resource type="Script" uid="uid://u6qhaped7c3i" path="res://scenes/ui/main_menu/main_menu.gd" id="1_pxqwl"]
[node name="MainMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_pxqwl")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 40
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 40
theme_override_constants/margin_bottom = 40
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 48
text = "Flashcard Thing"
horizontal_alignment = 1
[node name="Spacer" type="Control" parent="MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 60)
layout_mode = 2
[node name="MenuButtons" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 15
[node name="ManageDecksButton" type="Button" parent="MarginContainer/VBoxContainer/MenuButtons"]
custom_minimum_size = Vector2(300, 60)
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Manage Decks"
[node name="ViewProgressButton" type="Button" parent="MarginContainer/VBoxContainer/MenuButtons"]
custom_minimum_size = Vector2(300, 60)
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "View Progress"
[node name="QuitButton" type="Button" parent="MarginContainer/VBoxContainer/MenuButtons"]
custom_minimum_size = Vector2(300, 60)
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Quit"

View file

@ -0,0 +1,48 @@
extends PanelContainer
signal answer_submitted(is_correct: bool)
@onready var question_label = $MarginContainer/VBoxContainer/QuestionPanel/MarginContainer/QuestionLabel
@onready var answer_panel = $MarginContainer/VBoxContainer/AnswerPanel
@onready var answer_label = $MarginContainer/VBoxContainer/AnswerPanel/MarginContainer/AnswerLabel
@onready var show_answer_btn = $MarginContainer/VBoxContainer/Controls/ShowAnswerButton
@onready var rating_buttons = $MarginContainer/VBoxContainer/Controls/RatingButtons
@onready var incorrect_btn = $MarginContainer/VBoxContainer/Controls/RatingButtons/IncorrectButton
@onready var correct_btn = $MarginContainer/VBoxContainer/Controls/RatingButtons/CorrectButton
var current_card: Flashcard
var answer_revealed: bool = false
func _ready() -> void:
show_answer_btn.pressed.connect(_on_show_answer_pressed)
incorrect_btn.pressed.connect(_on_incorrect_pressed)
correct_btn.pressed.connect(_on_correct_pressed)
answer_panel.hide()
rating_buttons.hide()
func set_card(card: Flashcard) -> void:
current_card = card
answer_revealed = false
question_label.text = card.question_text
question_label.modulate = card.question_color
answer_label.text = card.answer_text
answer_label.modulate = card.answer_color
answer_panel.hide()
rating_buttons.hide()
show_answer_btn.show()
func _on_show_answer_pressed() -> void:
answer_revealed = true
answer_panel.show()
show_answer_btn.hide()
rating_buttons.show()
func _on_incorrect_pressed() -> void:
answer_submitted.emit(false)
func _on_correct_pressed() -> void:
answer_submitted.emit(true)

View file

@ -0,0 +1 @@
uid://lobty8kshx4c

View file

@ -0,0 +1,83 @@
[gd_scene load_steps=2 format=3 uid="uid://b4nnkfplcdxkp"]
[ext_resource type="Script" path="res://scenes/ui/quiz_mode/flashcard_display.gd" id="1_8xp2n"]
[node name="FlashcardDisplay" type="PanelContainer"]
custom_minimum_size = Vector2(600, 400)
script = ExtResource("1_8xp2n")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 30
theme_override_constants/margin_top = 30
theme_override_constants/margin_right = 30
theme_override_constants/margin_bottom = 30
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="QuestionPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/QuestionPanel"]
layout_mode = 2
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 20
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 20
[node name="QuestionLabel" type="Label" parent="MarginContainer/VBoxContainer/QuestionPanel/MarginContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Question goes here"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 3
[node name="AnswerPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/AnswerPanel"]
layout_mode = 2
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 20
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 20
[node name="AnswerLabel" type="Label" parent="MarginContainer/VBoxContainer/AnswerPanel/MarginContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Answer goes here"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 3
[node name="Controls" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="ShowAnswerButton" type="Button" parent="MarginContainer/VBoxContainer/Controls"]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "Show Answer"
[node name="RatingButtons" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Controls"]
layout_mode = 2
theme_override_constants/separation = 20
alignment = 1
[node name="IncorrectButton" type="Button" parent="MarginContainer/VBoxContainer/Controls/RatingButtons"]
custom_minimum_size = Vector2(200, 50)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "❌ Incorrect"
[node name="CorrectButton" type="Button" parent="MarginContainer/VBoxContainer/Controls/RatingButtons"]
custom_minimum_size = Vector2(200, 50)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "✓ Correct"

View file

@ -0,0 +1,67 @@
extends PanelContainer
signal answer_submitted(is_correct: bool)
@onready var question_label = $MarginContainer/VBoxContainer/QuestionPanel/MarginContainer/QuestionLabel
@onready var choices_container = $MarginContainer/VBoxContainer/ChoicesContainer
@onready var feedback_panel = $MarginContainer/VBoxContainer/FeedbackPanel
@onready var feedback_label = $MarginContainer/VBoxContainer/FeedbackPanel/MarginContainer/FeedbackLabel
var current_card: Flashcard
var choice_buttons: Array[Button] = []
var answered: bool = false
func _ready() -> void:
feedback_panel.hide()
func set_card(card: Flashcard) -> void:
current_card = card
answered = false
question_label.text = card.question_text
question_label.modulate = card.question_color
for button in choice_buttons:
button.queue_free()
choice_buttons.clear()
for i in range(card.choices.size()):
var choice = card.choices[i]
var button = Button.new()
button.text = choice.text
button.custom_minimum_size = Vector2(0, 50)
button.add_theme_font_size_override("font_size", 18)
button.pressed.connect(_on_choice_selected.bind(i))
choices_container.add_child(button)
choice_buttons.append(button)
feedback_panel.hide()
func _on_choice_selected(choice_index: int) -> void:
if answered:
return
answered = true
var is_correct = current_card.choices[choice_index].is_correct
for button in choice_buttons:
button.disabled = true
if is_correct:
choice_buttons[choice_index].modulate = Color.GREEN
feedback_label.text = "✓ Correct! " + current_card.answer_text
feedback_panel.self_modulate = Color(0.2, 0.6, 0.2, 1.0)
else:
choice_buttons[choice_index].modulate = Color.RED
var correct_index = current_card.get_correct_choice_index()
if correct_index >= 0:
choice_buttons[correct_index].modulate = Color.GREEN
feedback_label.text = "❌ Incorrect. The answer is: " + current_card.answer_text
feedback_panel.self_modulate = Color(0.6, 0.2, 0.2, 1.0)
feedback_panel.show()
await get_tree().create_timer(2.0).timeout
answer_submitted.emit(is_correct)

View file

@ -0,0 +1 @@
uid://2cccr5qtjk2c

View file

@ -0,0 +1,58 @@
[gd_scene load_steps=2 format=3 uid="uid://b0qexkdgxksek"]
[ext_resource type="Script" path="res://scenes/ui/quiz_mode/multiple_choice_display.gd" id="1_a2ghr"]
[node name="MultipleChoiceDisplay" type="PanelContainer"]
custom_minimum_size = Vector2(600, 400)
script = ExtResource("1_a2ghr")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 30
theme_override_constants/margin_top = 30
theme_override_constants/margin_right = 30
theme_override_constants/margin_bottom = 30
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="QuestionPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/QuestionPanel"]
layout_mode = 2
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 20
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 20
[node name="QuestionLabel" type="Label" parent="MarginContainer/VBoxContainer/QuestionPanel/MarginContainer"]
custom_minimum_size = Vector2(0, 80)
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Question goes here"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 3
[node name="ChoicesContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="FeedbackPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/FeedbackPanel"]
layout_mode = 2
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 15
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 15
[node name="FeedbackLabel" type="Label" parent="MarginContainer/VBoxContainer/FeedbackPanel/MarginContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "Feedback"
horizontal_alignment = 1
autowrap_mode = 3

View file

@ -0,0 +1,95 @@
extends Control
@onready var exit_btn = $MarginContainer/VBoxContainer/Header/ExitButton
@onready var progress_label = $MarginContainer/VBoxContainer/Header/ProgressLabel
@onready var score_label = $MarginContainer/VBoxContainer/ScorePanel/ScoreLabel
@onready var card_container = $MarginContainer/VBoxContainer/CardContainer
var quiz_session: QuizSession
var current_card_display: Node
const FlashcardDisplayScene = preload("res://scenes/ui/quiz_mode/flashcard_display.tscn")
const MultipleChoiceDisplayScene = preload("res://scenes/ui/quiz_mode/multiple_choice_display.tscn")
func _ready() -> void:
exit_btn.pressed.connect(_on_exit_pressed)
func start_quiz(deck: Deck) -> void:
if not deck or deck.cards.is_empty():
push_error("Cannot start quiz with empty or null deck")
return
quiz_session = QuizSession.new()
quiz_session.question_answered.connect(_on_question_answered)
quiz_session.session_completed.connect(_on_session_completed)
quiz_session.progress_updated.connect(_on_progress_updated)
quiz_session.start_session(deck, true)
var progress = DataManager.get_or_create_progress(deck.id)
progress.start_session()
DataManager.save_progress()
_show_next_card()
_update_ui()
func _show_next_card() -> void:
var card = quiz_session.get_current_card()
if not card:
return
if current_card_display:
current_card_display.queue_free()
current_card_display = null
if card.card_type == Flashcard.QuestionType.BASIC:
current_card_display = FlashcardDisplayScene.instantiate()
else:
current_card_display = MultipleChoiceDisplayScene.instantiate()
card_container.add_child(current_card_display)
current_card_display.set_card(card)
current_card_display.answer_submitted.connect(_on_answer_submitted)
func _on_answer_submitted(is_correct: bool) -> void:
var card = quiz_session.get_current_card()
if quiz_session.deck:
DataManager.record_quiz_result(quiz_session.deck.id, card.id, is_correct)
quiz_session.answer_question(is_correct)
func _on_question_answered(is_correct: bool, card: Flashcard) -> void:
await get_tree().create_timer(0.5).timeout
_update_ui()
_show_next_card()
func _on_progress_updated(current: int, total: int) -> void:
_update_ui()
func _on_session_completed(results: Dictionary) -> void:
print("Quiz completed! Results:")
print(" Total: %d" % results.total_questions)
print(" Correct: %d" % results.correct)
print(" Incorrect: %d" % results.incorrect)
print(" Accuracy: %.1f%%" % results.accuracy)
print(" Duration: %.1f seconds" % results.duration_seconds)
await get_tree().create_timer(1.0).timeout
GameManager.return_to_main_menu()
func _update_ui() -> void:
if quiz_session:
progress_label.text = "Card %d / %d" % [
quiz_session.current_index + 1,
quiz_session.shuffled_cards.size()
]
score_label.text = "Score: %d correct, %d incorrect (%.1f%%)" % [
quiz_session.session_correct,
quiz_session.session_incorrect,
quiz_session.get_session_accuracy()
]
func _on_exit_pressed() -> void:
GameManager.return_to_main_menu()

View file

@ -0,0 +1 @@
uid://sfunbafcelku

View file

@ -0,0 +1,57 @@
[gd_scene load_steps=2 format=3 uid="uid://bjldqy3e3m3yj"]
[ext_resource type="Script" path="res://scenes/ui/quiz_mode/quiz_scene.gd" id="1_vu2ha"]
[node name="QuizScene" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_vu2ha")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 40
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 40
theme_override_constants/margin_bottom = 40
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="Header" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="ExitButton" type="Button" parent="MarginContainer/VBoxContainer/Header"]
custom_minimum_size = Vector2(100, 50)
layout_mode = 2
text = "Exit Quiz"
[node name="ProgressLabel" type="Label" parent="MarginContainer/VBoxContainer/Header"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 24
text = "Card 1 / 10"
horizontal_alignment = 1
vertical_alignment = 1
[node name="ScorePanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="ScoreLabel" type="Label" parent="MarginContainer/VBoxContainer/ScorePanel"]
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "Score: 0 correct, 0 incorrect (0.0%)"
horizontal_alignment = 1
[node name="CardContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3