Study screen now matches the provided mockup's layout: a "Current Deck" panel with a circular mastery-progress ring, and a "Spaced Repetition" panel (New/Due/Learning/Review counts, streak, daily goal) on either side of the card. Kept the app's real global sidebar rather than the mockup's own nav, per direction. Mastery/Streak/Daily Goal weren't tracked anywhere, so this adds real backend support rather than showing fabricated numbers: - Mastered = graduated to review state AND FSRS stability >= 21 days (FlashcardScheduleStore.MasteryStabilityDaysThreshold), stricter than the plain Review-state count - CurrentStreakDays = consecutive UTC days with a review, computed from review-log timestamps in the new pure FlashcardScheduling .ComputeStreakDays (unit tested, habit-streak semantics: doesn't reset to 0 the instant a new day starts before today's first review) - DailyGoal/ReviewsToday reuse the account's existing ReviewsPerDay setting rather than inventing a new one Also removes the exit "X" button in favor of the same Escape/pause menu the question-player flow uses (main.gd's shared app_menu, with an "Exit Study Session" action), and adds a new circular_progress.gd reusable ring control plus a real "Options" menu (bury/suspend the current card) wired to schedule endpoints that had no UI before.
51 lines
1.7 KiB
GDScript
51 lines
1.7 KiB
GDScript
extends Control
|
|
|
|
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
|
|
|
var ring_value := 0.0
|
|
var track_color := Color("26324a")
|
|
var fill_color := Color("2563eb")
|
|
var ring_width := 8.0
|
|
var value_label: Label
|
|
var caption_label: Label
|
|
|
|
|
|
func _init() -> void:
|
|
custom_minimum_size = Vector2(112, 112)
|
|
var center := CenterContainer.new()
|
|
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(center)
|
|
var stack := VBoxContainer.new()
|
|
stack.add_theme_constant_override("separation", 0)
|
|
center.add_child(stack)
|
|
value_label = Label.new()
|
|
value_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
value_label.add_theme_font_size_override("font_size", 24)
|
|
stack.add_child(value_label)
|
|
caption_label = Label.new()
|
|
caption_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
caption_label.add_theme_font_size_override("font_size", 12)
|
|
stack.add_child(caption_label)
|
|
|
|
|
|
func configure(fill_fraction: float, value_text: String, caption_text: String, accent: Color, dark: bool) -> void:
|
|
ring_value = clampf(fill_fraction, 0.0, 1.0)
|
|
track_color = Color("26324a") if dark else Color("e2e8f0")
|
|
fill_color = accent
|
|
value_label.text = value_text
|
|
value_label.add_theme_color_override("font_color", Brand.text_color(dark))
|
|
caption_label.text = caption_text
|
|
caption_label.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
queue_redraw()
|
|
|
|
|
|
func _draw() -> void:
|
|
var radius := minf(size.x, size.y) / 2.0 - ring_width / 2.0
|
|
if radius <= 0.0:
|
|
return
|
|
var center := size / 2.0
|
|
draw_arc(center, radius, 0.0, TAU, 64, track_color, ring_width, true)
|
|
if ring_value > 0.0:
|
|
draw_arc(center, radius, -PI / 2.0, -PI / 2.0 + TAU * ring_value, 64, fill_color, ring_width, true)
|