Redesign study navigation and light theme

This commit is contained in:
hz
2026-07-16 02:37:37 -05:00
parent 0eca0c4cbf
commit 6d1787b3eb
57 changed files with 2629 additions and 301 deletions
+118 -44
View File
@@ -8,6 +8,7 @@ var main: Control
var auth_base_url := AUTH_BASE
var background: ColorRect
var left_brand: VBoxContainer
var brand_tagline: RichTextLabel
var auth_panel: PanelContainer
var panel_content: VBoxContainer
var heading: Label
@@ -63,8 +64,9 @@ func _ready() -> void:
poll_timer.timeout.connect(_poll_steam)
add_child(poll_timer)
get_viewport().size_changed.connect(_update_responsive_layout)
resized.connect(_update_responsive_layout)
UIThemeService.theme_changed.connect(_on_theme_changed)
_update_responsive_layout()
_update_responsive_layout.call_deferred()
_set_mode("login")
_apply_theme()
@@ -87,6 +89,7 @@ func _build_ui() -> void:
add_child(background)
_add_dot_grid(Vector2(22, 24))
_add_dot_grid(Vector2(22, 840))
theme_button = Button.new()
theme_button.tooltip_text = "Switch color theme"
@@ -98,14 +101,14 @@ func _build_ui() -> void:
var page_margin := MarginContainer.new()
page_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
page_margin.add_theme_constant_override("margin_left", 42)
page_margin.add_theme_constant_override("margin_right", 42)
page_margin.add_theme_constant_override("margin_top", 38)
page_margin.add_theme_constant_override("margin_bottom", 38)
page_margin.add_theme_constant_override("margin_left", 52)
page_margin.add_theme_constant_override("margin_right", 52)
page_margin.add_theme_constant_override("margin_top", 56)
page_margin.add_theme_constant_override("margin_bottom", 56)
add_child(page_margin)
var columns := HBoxContainer.new()
columns.add_theme_constant_override("separation", 54)
columns.add_theme_constant_override("separation", 72)
page_margin.add_child(columns)
left_brand = VBoxContainer.new()
@@ -119,35 +122,50 @@ func _build_ui() -> void:
left_brand.add_child(brand_row)
var large_icon := TextureRect.new()
large_icon.texture = preload("res://assets/branding/medmind-icon.png")
large_icon.custom_minimum_size = Vector2(172, 172)
large_icon.custom_minimum_size = Vector2(190, 190)
large_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
large_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
large_icon.material = Brand.logo_cutout_material()
brand_row.add_child(large_icon)
var wordmark := Label.new()
wordmark.text = "MedMind"
wordmark.add_theme_font_size_override("font_size", 76)
wordmark.add_theme_font_override("font", Brand.font)
var wordmark := HBoxContainer.new()
wordmark.add_theme_constant_override("separation", 0)
brand_row.add_child(wordmark)
var tagline := Label.new()
tagline.text = "Think like a physician."
tagline.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
tagline.add_theme_font_size_override("font_size", 22)
tagline.add_theme_font_override("font", Brand.font)
left_brand.add_child(tagline)
var med_wordmark := Label.new()
med_wordmark.text = "Med"
med_wordmark.add_theme_font_size_override("font_size", 76)
med_wordmark.add_theme_font_override("font", Brand.font)
wordmark.add_child(med_wordmark)
var mind_wordmark := Label.new()
mind_wordmark.text = "Mind"
mind_wordmark.add_theme_font_size_override("font_size", 76)
mind_wordmark.add_theme_font_override("font", Brand.font)
mind_wordmark.add_theme_color_override("font_color", Brand.PURPLE)
mind_wordmark.set_meta("brand_accent", true)
wordmark.add_child(mind_wordmark)
brand_tagline = RichTextLabel.new()
brand_tagline.bbcode_enabled = true
brand_tagline.fit_content = true
brand_tagline.scroll_active = false
brand_tagline.text = "Focused practice. Better decisions.\n[color=#2563eb]Step 3[/color] success."
brand_tagline.add_theme_font_override("normal_font", Brand.font)
brand_tagline.add_theme_font_size_override("normal_font_size", 20)
brand_tagline.custom_minimum_size = Vector2(520, 64)
brand_tagline.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
left_brand.add_child(brand_tagline)
var panel_center := CenterContainer.new()
panel_center.size_flags_horizontal = Control.SIZE_EXPAND_FILL
panel_center.size_flags_vertical = Control.SIZE_EXPAND_FILL
columns.add_child(panel_center)
auth_panel = PanelContainer.new()
auth_panel.custom_minimum_size = Vector2(610, 760)
auth_panel.custom_minimum_size = Vector2(710, 820)
panel_center.add_child(auth_panel)
var scroll := ScrollContainer.new()
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
auth_panel.add_child(scroll)
panel_content = VBoxContainer.new()
panel_content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
panel_content.add_theme_constant_override("separation", 16)
panel_content.add_theme_constant_override("separation", 24)
scroll.add_child(panel_content)
var panel_header := HBoxContainer.new()
@@ -155,19 +173,20 @@ func _build_ui() -> void:
panel_content.add_child(panel_header)
var small_icon := TextureRect.new()
small_icon.texture = preload("res://assets/branding/medmind-icon.png")
small_icon.custom_minimum_size = Vector2(82, 82)
small_icon.custom_minimum_size = Vector2(72, 72)
small_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
small_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
small_icon.material = Brand.logo_cutout_material()
panel_header.add_child(small_icon)
var titles := VBoxContainer.new()
titles.size_flags_horizontal = Control.SIZE_EXPAND_FILL
panel_header.add_child(titles)
heading = Label.new()
heading.add_theme_font_size_override("font_size", 34)
heading.add_theme_font_size_override("font_size", 32)
heading.add_theme_font_override("font", Brand.font)
titles.add_child(heading)
subheading = Label.new()
subheading.add_theme_font_size_override("font_size", 18)
subheading.add_theme_font_size_override("font_size", 20)
subheading.add_theme_font_override("font", Brand.font)
titles.add_child(subheading)
@@ -190,7 +209,7 @@ func _build_login_view() -> VBoxContainer:
var view := _new_form()
login_input = _add_field(view, "Email or Username", "Enter your email or username")
password_input = _add_field(view, "Password", "Enter your password", true)
_add_show_password(view, [password_input])
_add_password_reveal(password_input)
var forgot := Button.new()
forgot.text = "Forgot password?"
forgot.flat = true
@@ -199,13 +218,10 @@ func _build_login_view() -> VBoxContainer:
view.add_child(forgot)
var sign_in := _add_primary_button(view, "Sign In", _on_login_pressed)
password_input.text_submitted.connect(func(_text: String): _on_login_pressed())
var divider := Label.new()
divider.text = "or"
divider.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
view.add_child(divider)
view.add_child(_build_divider())
var steam := Button.new()
steam.text = "Sign in with Steam"
steam.custom_minimum_size = Vector2(0, 58)
steam.custom_minimum_size = Vector2(0, 68)
steam.pressed.connect(_start_steam)
view.add_child(steam)
submit_buttons.append(steam)
@@ -270,10 +286,26 @@ func _build_steam_view() -> VBoxContainer:
func _new_form() -> VBoxContainer:
var form := VBoxContainer.new()
form.add_theme_constant_override("separation", 12)
form.add_theme_constant_override("separation", 16)
return form
func _build_divider() -> HBoxContainer:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 22)
for index in range(3):
if index == 1:
var label := Label.new()
label.text = "or"
label.add_theme_font_override("font", Brand.font)
row.add_child(label)
else:
var line := HSeparator.new()
line.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_child(line)
return row
func _add_field(parent: VBoxContainer, label_text: String, placeholder: String, secret := false) -> LineEdit:
var label := Label.new()
label.text = label_text
@@ -283,8 +315,8 @@ func _add_field(parent: VBoxContainer, label_text: String, placeholder: String,
var input := LineEdit.new()
input.placeholder_text = placeholder
input.secret = secret
input.custom_minimum_size = Vector2(0, 54)
input.add_theme_font_size_override("font_size", 16)
input.custom_minimum_size = Vector2(0, 68)
input.add_theme_font_size_override("font_size", 17)
input.add_theme_font_override("font", Brand.font)
parent.add_child(input)
fields.append(input)
@@ -294,17 +326,33 @@ func _add_field(parent: VBoxContainer, label_text: String, placeholder: String,
func _add_show_password(parent: VBoxContainer, targets: Array) -> void:
var toggle := CheckButton.new()
toggle.text = "Show password"
toggle.toggled.connect(func(show: bool):
toggle.toggled.connect(func(should_reveal: bool):
for target in targets:
target.secret = not show
target.secret = not should_reveal
)
parent.add_child(toggle)
func _add_password_reveal(input: LineEdit) -> void:
var reveal := Button.new()
reveal.text = "\u25c9"
reveal.tooltip_text = "Show password"
reveal.flat = true
reveal.focus_mode = Control.FOCUS_NONE
reveal.custom_minimum_size = Vector2(52, 52)
reveal.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
reveal.position = Vector2(-60, -26)
reveal.pressed.connect(func():
input.secret = not input.secret
reveal.tooltip_text = "Show password" if input.secret else "Hide password"
)
input.add_child(reveal)
func _add_primary_button(parent: VBoxContainer, text_value: String, callback: Callable) -> Button:
var button := Button.new()
button.text = text_value
button.custom_minimum_size = Vector2(0, 58)
button.custom_minimum_size = Vector2(0, 68)
button.pressed.connect(callback)
parent.add_child(button)
submit_buttons.append(button)
@@ -349,17 +397,28 @@ func _set_mode(next_mode: String) -> void:
func _update_responsive_layout() -> void:
if left_brand == null or layout_updating: return
layout_updating = true
var narrow := size.x < 1120.0
var layout_width := size.x
if layout_width <= 0.0:
layout_width = get_viewport_rect().size.x
var compact_desktop := layout_width < 900.0
var mobile_web := false
if OS.has_feature("web"):
var browser_width: Variant = JavaScriptBridge.eval("window.innerWidth", true)
narrow = float(browser_width) < 760.0
left_brand.visible = not narrow
compact_layout = narrow
theme_button.custom_minimum_size = Vector2(160, 160) if narrow else Vector2(44, 44)
theme_button.position = Vector2(-190, 20) if narrow else Vector2(-58, 14)
theme_button.add_theme_font_size_override("font_size", 64 if narrow else 20)
auth_panel.custom_minimum_size = Vector2(1700, 2300) if narrow else Vector2(610, 780)
_apply_compact_scale(panel_content, 3.8 if narrow else 1.0)
mobile_web = float(browser_width) < 760.0
compact_layout = compact_desktop or mobile_web
left_brand.visible = not compact_layout
if mobile_web:
theme_button.custom_minimum_size = Vector2(160, 160)
theme_button.position = Vector2(-190, 20)
theme_button.add_theme_font_size_override("font_size", 64)
auth_panel.custom_minimum_size = Vector2(1700, 2300)
_apply_compact_scale(panel_content, 3.8)
else:
theme_button.custom_minimum_size = Vector2(44, 44)
theme_button.position = Vector2(-58, 14)
theme_button.add_theme_font_size_override("font_size", 20)
auth_panel.custom_minimum_size = Vector2(640, 790) if compact_desktop else Vector2(710, 820)
_apply_compact_scale(panel_content, 1.0)
_apply_theme()
layout_updating = false
@@ -381,12 +440,17 @@ func _apply_compact_scale(control: Control, factor: float) -> void:
func _apply_theme() -> void:
var dark := UIThemeService.is_dark_mode()
background.color = Brand.background_color(dark)
background.material = Brand.background_material(dark)
auth_panel.add_theme_stylebox_override("panel", Brand.panel_box(dark))
theme_button.text = "☀" if dark else "☾"
theme_button.tooltip_text = "Use light theme" if dark else "Use dark theme"
theme_button.add_theme_font_override("font", Brand.font)
for node in find_children("*", "Label", true, false):
(node as Label).add_theme_color_override("font_color", Brand.text_color(dark))
var label := node as Label
if label.has_meta("brand_accent"):
label.add_theme_color_override("font_color", Brand.PURPLE if dark else Brand.ROYAL_BLUE)
else:
label.add_theme_color_override("font_color", Brand.text_color(dark))
for input in fields:
input.add_theme_color_override("font_color", Brand.text_color(dark))
input.add_theme_color_override("font_placeholder_color", Brand.muted_color(dark))
@@ -403,8 +467,18 @@ func _apply_theme() -> void:
if button.text not in ["Sign in with Steam"]:
button.add_theme_stylebox_override("normal", Brand.gradient_button_box())
button.add_theme_color_override("font_color", Color.WHITE)
var theme_normal := StyleBoxFlat.new()
theme_normal.bg_color = Color("111b30") if dark else Brand.LIGHT_SURFACE
theme_normal.border_color = Color("33415d") if dark else Brand.LIGHT_BORDER
theme_normal.set_border_width_all(1)
theme_normal.set_corner_radius_all(22)
theme_button.add_theme_stylebox_override("normal", theme_normal)
theme_button.add_theme_stylebox_override("hover", theme_normal)
theme_button.add_theme_color_override("font_color", Brand.AMBER)
subheading.add_theme_color_override("font_color", Brand.muted_color(dark))
message_label.add_theme_color_override("font_color", Brand.muted_color(dark))
if brand_tagline != null:
brand_tagline.add_theme_color_override("default_color", Brand.muted_color(dark))
func _on_theme_changed(_is_dark: bool) -> void: