519 lines
19 KiB
GDScript
519 lines
19 KiB
GDScript
class_name CourseBoardPanel
|
||
extends Control
|
||
|
||
# ============================================================================
|
||
# CourseBoardPanel.gd - Datawhale 课程看板
|
||
# ============================================================================
|
||
# 展示 Datawhale 学习中心课程列表,由打工区 VirtualWhaleRecruitmentBoard 打开。
|
||
# ============================================================================
|
||
|
||
const MOVEMENT_ACTIONS: Array[String] = ["move_left", "move_right", "move_up", "move_down"]
|
||
const API_PATH: String = "/course-resources/datawhale"
|
||
const REQUEST_TIMEOUT: float = 18.0
|
||
const PANEL_SIZE: Vector2 = Vector2(1500, 1020)
|
||
const MIN_MARGIN: Vector2 = Vector2(24, 24)
|
||
const CARD_SIZE: Vector2 = Vector2(320, 348)
|
||
const COVER_HEIGHT: float = 171.0
|
||
const TEXT_COLOR: Color = Color(0.098, 0.140, 0.190)
|
||
const MUTED_COLOR: Color = Color(0.380, 0.450, 0.540)
|
||
const ACCENT_COLOR: Color = Color(0.000, 0.322, 0.851)
|
||
const SOFT_BLUE: Color = Color(0.925, 0.965, 1.0, 1.0)
|
||
const IMAGE_PLACEHOLDER: Color = Color(1.0, 0.965, 0.870, 1.0)
|
||
|
||
var _overlay: ColorRect
|
||
var _panel: Control
|
||
var _grid: GridContainer
|
||
var _statusLabel: Label
|
||
var _isOpen: bool = false
|
||
var _transitionTween: Tween
|
||
|
||
func _ready() -> void:
|
||
add_to_group("whaletown_escape_dismissible")
|
||
visible = false
|
||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
set_process(false)
|
||
_buildUi()
|
||
|
||
func _notification(what: int) -> void:
|
||
if what == NOTIFICATION_RESIZED and is_instance_valid(_panel):
|
||
_positionPanel()
|
||
|
||
func _input(event: InputEvent) -> void:
|
||
if get_viewport().is_input_handled():
|
||
return
|
||
if not _isOpen:
|
||
return
|
||
if event is InputEventKey:
|
||
var keyEvent := event as InputEventKey
|
||
if keyEvent.pressed and not keyEvent.echo and keyEvent.keycode == KEY_ESCAPE:
|
||
hide_panel()
|
||
get_viewport().set_input_as_handled()
|
||
|
||
func _process(_delta: float) -> void:
|
||
if _isOpen:
|
||
_releaseMovementActions()
|
||
|
||
func show_panel() -> void:
|
||
if _isOpen:
|
||
return
|
||
_isOpen = true
|
||
visible = true
|
||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||
set_process(true)
|
||
_releaseMovementInputState()
|
||
var taskManager := get_node_or_null("/root/TaskManager")
|
||
if taskManager != null and taskManager.has_method("report_activity"):
|
||
taskManager.call("report_activity", "course_board_opened")
|
||
_fetchCourses()
|
||
_animatePanel(true)
|
||
|
||
func hide_panel() -> void:
|
||
if not _isOpen:
|
||
return
|
||
_isOpen = false
|
||
_releaseMovementInputState()
|
||
_animatePanel(false)
|
||
|
||
func is_panel_open() -> bool:
|
||
return _isOpen
|
||
|
||
func is_escape_dismissible() -> bool:
|
||
return _isOpen
|
||
|
||
func get_escape_priority() -> int:
|
||
return 700
|
||
|
||
func request_escape_close() -> void:
|
||
hide_panel()
|
||
|
||
func _buildUi() -> void:
|
||
_overlay = ColorRect.new()
|
||
_overlay.name = "courseBoardDimOverlay"
|
||
_overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
_overlay.color = Color(0.020, 0.036, 0.060, 0.64)
|
||
_overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
_overlay.gui_input.connect(_onOverlayGuiInput)
|
||
add_child(_overlay)
|
||
|
||
_panel = Control.new()
|
||
_panel.name = "courseBoardPanel"
|
||
_panel.custom_minimum_size = PANEL_SIZE
|
||
_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
add_child(_panel)
|
||
_positionPanel()
|
||
|
||
var frame := PanelContainer.new()
|
||
frame.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
frame.add_theme_stylebox_override("panel", _createPanelStyle(Color(0.972, 0.988, 1.0, 1.0), 26, true))
|
||
frame.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_panel.add_child(frame)
|
||
|
||
var margin := MarginContainer.new()
|
||
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
margin.add_theme_constant_override("margin_left", 34)
|
||
margin.add_theme_constant_override("margin_top", 28)
|
||
margin.add_theme_constant_override("margin_right", 34)
|
||
margin.add_theme_constant_override("margin_bottom", 28)
|
||
_panel.add_child(margin)
|
||
|
||
var root := VBoxContainer.new()
|
||
root.add_theme_constant_override("separation", 18)
|
||
margin.add_child(root)
|
||
root.add_child(_buildHeader())
|
||
root.add_child(_buildGridPanel())
|
||
root.add_child(_buildFooter())
|
||
|
||
_updatePanelAlpha(0.0, 0.0)
|
||
|
||
func _buildHeader() -> Control:
|
||
var header := PanelContainer.new()
|
||
header.custom_minimum_size = Vector2(0, 106)
|
||
header.add_theme_stylebox_override("panel", _createPanelStyle(Color(0.000, 0.322, 0.851, 0.96), 24, false))
|
||
|
||
var margin := MarginContainer.new()
|
||
margin.add_theme_constant_override("margin_left", 28)
|
||
margin.add_theme_constant_override("margin_top", 12)
|
||
margin.add_theme_constant_override("margin_right", 20)
|
||
margin.add_theme_constant_override("margin_bottom", 12)
|
||
header.add_child(margin)
|
||
|
||
var row := HBoxContainer.new()
|
||
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||
row.add_theme_constant_override("separation", 16)
|
||
margin.add_child(row)
|
||
|
||
var titleStack := VBoxContainer.new()
|
||
titleStack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
titleStack.add_theme_constant_override("separation", 4)
|
||
row.add_child(titleStack)
|
||
|
||
var title := Label.new()
|
||
title.text = "Datawhale 课程看板"
|
||
title.add_theme_font_size_override("font_size", 32)
|
||
title.add_theme_color_override("font_color", Color.WHITE)
|
||
titleStack.add_child(title)
|
||
|
||
var subtitle := Label.new()
|
||
subtitle.text = "AI学习中心最新课程"
|
||
subtitle.add_theme_font_size_override("font_size", 18)
|
||
subtitle.add_theme_color_override("font_color", Color(0.835, 0.925, 1.0, 1.0))
|
||
titleStack.add_child(subtitle)
|
||
|
||
var closeButton := Button.new()
|
||
closeButton.text = "×"
|
||
closeButton.custom_minimum_size = Vector2(54, 54)
|
||
closeButton.focus_mode = Control.FOCUS_NONE
|
||
closeButton.add_theme_font_size_override("font_size", 30)
|
||
closeButton.add_theme_color_override("font_color", Color.WHITE)
|
||
closeButton.add_theme_stylebox_override("normal", _createButtonStyle(Color(1.0, 1.0, 1.0, 0.12)))
|
||
closeButton.add_theme_stylebox_override("hover", _createButtonStyle(Color(1.0, 1.0, 1.0, 0.22)))
|
||
closeButton.add_theme_stylebox_override("pressed", _createButtonStyle(Color(1.0, 1.0, 1.0, 0.30)))
|
||
closeButton.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||
closeButton.pressed.connect(hide_panel)
|
||
row.add_child(closeButton)
|
||
return header
|
||
|
||
func _buildGridPanel() -> Control:
|
||
var holder := PanelContainer.new()
|
||
holder.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
holder.add_theme_stylebox_override("panel", _createPanelStyle(Color(0.948, 0.978, 1.0, 0.92), 20, false))
|
||
|
||
var margin := MarginContainer.new()
|
||
margin.add_theme_constant_override("margin_left", 18)
|
||
margin.add_theme_constant_override("margin_top", 18)
|
||
margin.add_theme_constant_override("margin_right", 18)
|
||
margin.add_theme_constant_override("margin_bottom", 18)
|
||
holder.add_child(margin)
|
||
|
||
var scroll := ScrollContainer.new()
|
||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
|
||
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
margin.add_child(scroll)
|
||
|
||
_grid = GridContainer.new()
|
||
_grid.columns = 4
|
||
_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_grid.add_theme_constant_override("h_separation", 24)
|
||
_grid.add_theme_constant_override("v_separation", 24)
|
||
scroll.add_child(_grid)
|
||
return holder
|
||
|
||
func _buildFooter() -> Control:
|
||
var footer := HBoxContainer.new()
|
||
footer.custom_minimum_size = Vector2(0, 34)
|
||
footer.alignment = BoxContainer.ALIGNMENT_CENTER
|
||
|
||
_statusLabel = Label.new()
|
||
_statusLabel.text = "靠近招募看板按 E 打开,点击课程卡片进入详情"
|
||
_statusLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_statusLabel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_statusLabel.add_theme_font_size_override("font_size", 16)
|
||
_statusLabel.add_theme_color_override("font_color", MUTED_COLOR)
|
||
footer.add_child(_statusLabel)
|
||
return footer
|
||
|
||
func _fetchCourses() -> void:
|
||
_statusLabel.text = "正在同步 Datawhale 课程..."
|
||
_clearChildren(_grid)
|
||
var api_client := get_node_or_null("/root/ApiClient")
|
||
if api_client == null or not api_client.has_method("get_json"):
|
||
_statusLabel.text = "课程服务不可用"
|
||
return
|
||
api_client.call("get_json", API_PATH, _onRequestCompleted, false, REQUEST_TIMEOUT)
|
||
|
||
func _onRequestCompleted(success: bool, payload: Dictionary, error_info: Dictionary) -> void:
|
||
if not success:
|
||
_statusLabel.text = str(error_info.get("message", "课程加载失败,请稍后再试"))
|
||
return
|
||
|
||
var data: Variant = payload.get("data", {})
|
||
if not data is Dictionary:
|
||
_statusLabel.text = "课程数据为空"
|
||
return
|
||
|
||
var coursesVariant: Variant = (data as Dictionary).get("courses", [])
|
||
if not coursesVariant is Array:
|
||
_statusLabel.text = "课程数据为空"
|
||
return
|
||
|
||
var courses: Array = coursesVariant as Array
|
||
_renderCourses(courses)
|
||
_statusLabel.text = "已同步 %d 门课程" % courses.size()
|
||
|
||
func _renderCourses(rows: Array) -> void:
|
||
_clearChildren(_grid)
|
||
for row in rows:
|
||
if row is Dictionary:
|
||
_grid.add_child(_buildCourseCard(row as Dictionary))
|
||
|
||
func _buildCourseCard(course: Dictionary) -> Button:
|
||
var card := Button.new()
|
||
card.custom_minimum_size = CARD_SIZE
|
||
card.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
card.focus_mode = Control.FOCUS_NONE
|
||
card.clip_contents = true
|
||
card.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
card.add_theme_stylebox_override("normal", _createCardStyle(false))
|
||
card.add_theme_stylebox_override("hover", _createCardStyle(true))
|
||
card.add_theme_stylebox_override("pressed", _createCardStyle(true))
|
||
card.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||
card.pressed.connect(_openCourse.bind(course))
|
||
|
||
var root := VBoxContainer.new()
|
||
root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
root.add_theme_constant_override("separation", 0)
|
||
root.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
card.add_child(root)
|
||
|
||
var coverPanel := Control.new()
|
||
coverPanel.custom_minimum_size = Vector2(0, COVER_HEIGHT)
|
||
coverPanel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
coverPanel.clip_contents = true
|
||
coverPanel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
root.add_child(coverPanel)
|
||
|
||
var coverBg := ColorRect.new()
|
||
coverBg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
coverBg.color = IMAGE_PLACEHOLDER
|
||
coverBg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
coverPanel.add_child(coverBg)
|
||
|
||
var coverImage := TextureRect.new()
|
||
coverImage.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
coverImage.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||
coverImage.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
|
||
coverImage.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
coverPanel.add_child(coverImage)
|
||
|
||
var coverText := Label.new()
|
||
coverText.text = _shortTitle(str(course.get("title", "课程")))
|
||
coverText.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
coverText.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
coverText.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
coverText.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
coverText.add_theme_font_size_override("font_size", 22)
|
||
coverText.add_theme_color_override("font_color", ACCENT_COLOR)
|
||
coverText.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
coverPanel.add_child(coverText)
|
||
_loadCoverImage(str(course.get("coverUrl", "")), coverImage, coverText)
|
||
|
||
var bodyMargin := MarginContainer.new()
|
||
bodyMargin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
bodyMargin.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
bodyMargin.add_theme_constant_override("margin_left", 16)
|
||
bodyMargin.add_theme_constant_override("margin_top", 16)
|
||
bodyMargin.add_theme_constant_override("margin_right", 16)
|
||
bodyMargin.add_theme_constant_override("margin_bottom", 30)
|
||
bodyMargin.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
root.add_child(bodyMargin)
|
||
|
||
var info := VBoxContainer.new()
|
||
info.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
info.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
info.clip_contents = true
|
||
info.add_theme_constant_override("separation", 6)
|
||
info.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
bodyMargin.add_child(info)
|
||
|
||
var title := Label.new()
|
||
title.text = str(course.get("title", "未命名课程"))
|
||
title.custom_minimum_size = Vector2(0, 46)
|
||
title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
title.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
title.add_theme_font_size_override("font_size", 18)
|
||
title.add_theme_color_override("font_color", TEXT_COLOR)
|
||
title.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
info.add_child(title)
|
||
|
||
var intro := Label.new()
|
||
intro.text = str(course.get("intro", ""))
|
||
intro.custom_minimum_size = Vector2(0, 58)
|
||
intro.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
intro.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
intro.add_theme_font_size_override("font_size", 13)
|
||
intro.add_theme_color_override("font_color", MUTED_COLOR)
|
||
intro.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
info.add_child(intro)
|
||
|
||
var spacer := Control.new()
|
||
spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
info.add_child(spacer)
|
||
|
||
var meta := HFlowContainer.new()
|
||
meta.custom_minimum_size = Vector2(0, 34)
|
||
meta.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
meta.add_theme_constant_override("h_separation", 6)
|
||
meta.add_theme_constant_override("v_separation", 6)
|
||
meta.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
info.add_child(meta)
|
||
meta.add_child(_buildTag(str(course.get("difficultyLabel", "其他")), Color(0.918, 0.960, 1.0, 1.0), ACCENT_COLOR, 56.0))
|
||
meta.add_child(_buildTag(str(course.get("categoryLabel", "其他")), Color(0.945, 0.950, 0.965, 1.0), MUTED_COLOR, 112.0))
|
||
|
||
var views := int(course.get("viewCount", 0))
|
||
if views > 0:
|
||
meta.add_child(_buildTag(_formatViews(views), Color(0.950, 0.980, 0.965, 1.0), Color(0.160, 0.510, 0.340), 82.0))
|
||
|
||
return card
|
||
|
||
func _buildTag(text: String, bgColor: Color, fontColor: Color, maxWidth: float) -> Label:
|
||
var tag := Label.new()
|
||
tag.text = text
|
||
tag.custom_minimum_size = Vector2(maxWidth, 26)
|
||
tag.clip_text = true
|
||
tag.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
tag.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
tag.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
tag.add_theme_font_size_override("font_size", 12)
|
||
tag.add_theme_color_override("font_color", fontColor)
|
||
tag.add_theme_stylebox_override("normal", _createPanelStyle(bgColor, 12, false))
|
||
tag.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
return tag
|
||
|
||
func _loadCoverImage(url: String, textureRect: TextureRect, fallbackLabel: Label) -> void:
|
||
var coverUrl := url.strip_edges()
|
||
if coverUrl.is_empty():
|
||
return
|
||
|
||
var request := HTTPRequest.new()
|
||
request.timeout = 12.0
|
||
request.request_completed.connect(_onCoverRequestCompleted.bind(textureRect, fallbackLabel, request))
|
||
add_child(request)
|
||
var err := request.request(coverUrl)
|
||
if err != OK:
|
||
request.queue_free()
|
||
|
||
func _onCoverRequestCompleted(result: int, responseCode: int, _headers: PackedStringArray, body: PackedByteArray, textureRect: TextureRect, fallbackLabel: Label, request: HTTPRequest) -> void:
|
||
if is_instance_valid(request):
|
||
request.queue_free()
|
||
if not is_instance_valid(textureRect):
|
||
return
|
||
if result != HTTPRequest.RESULT_SUCCESS or responseCode < 200 or responseCode >= 300:
|
||
return
|
||
|
||
var image := Image.new()
|
||
var loadError := image.load_png_from_buffer(body)
|
||
if loadError != OK:
|
||
loadError = image.load_jpg_from_buffer(body)
|
||
if loadError != OK:
|
||
loadError = image.load_webp_from_buffer(body)
|
||
if loadError != OK:
|
||
return
|
||
|
||
textureRect.texture = ImageTexture.create_from_image(image)
|
||
if is_instance_valid(fallbackLabel):
|
||
fallbackLabel.visible = false
|
||
|
||
func _openCourse(course: Dictionary) -> void:
|
||
var detailUrl := str(course.get("detailUrl", "")).strip_edges()
|
||
if detailUrl.is_empty():
|
||
return
|
||
OS.shell_open(detailUrl)
|
||
|
||
func _onOverlayGuiInput(event: InputEvent) -> void:
|
||
if event is InputEventMouseButton:
|
||
var mouseEvent := event as InputEventMouseButton
|
||
if mouseEvent.pressed and mouseEvent.button_index == MOUSE_BUTTON_LEFT:
|
||
hide_panel()
|
||
|
||
func _positionPanel() -> void:
|
||
var viewportSize := get_viewport_rect().size
|
||
var width := minf(PANEL_SIZE.x, maxf(980.0, viewportSize.x - MIN_MARGIN.x * 2.0))
|
||
var height := minf(PANEL_SIZE.y, maxf(760.0, viewportSize.y - MIN_MARGIN.y * 2.0))
|
||
_panel.position = (viewportSize - Vector2(width, height)) * 0.5
|
||
_panel.size = Vector2(width, height)
|
||
_panel.pivot_offset = _panel.size * 0.5
|
||
|
||
func _animatePanel(opening: bool) -> void:
|
||
if is_instance_valid(_transitionTween):
|
||
_transitionTween.kill()
|
||
_transitionTween = create_tween()
|
||
_transitionTween.set_parallel(true)
|
||
if opening:
|
||
_updatePanelAlpha(0.0, 0.0)
|
||
_panel.scale = Vector2(0.985, 0.985)
|
||
_transitionTween.tween_property(_overlay, "modulate:a", 1.0, 0.16)
|
||
_transitionTween.tween_property(_panel, "modulate:a", 1.0, 0.18)
|
||
_transitionTween.tween_property(_panel, "scale", Vector2.ONE, 0.18)
|
||
else:
|
||
_transitionTween.tween_property(_overlay, "modulate:a", 0.0, 0.12)
|
||
_transitionTween.tween_property(_panel, "modulate:a", 0.0, 0.12)
|
||
_transitionTween.tween_property(_panel, "scale", Vector2(0.985, 0.985), 0.12)
|
||
_transitionTween.finished.connect(_onCloseAnimationFinished)
|
||
|
||
func _onCloseAnimationFinished() -> void:
|
||
if _isOpen:
|
||
return
|
||
visible = false
|
||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
set_process(false)
|
||
|
||
func _updatePanelAlpha(overlayAlpha: float, panelAlpha: float) -> void:
|
||
if is_instance_valid(_overlay):
|
||
_overlay.modulate.a = overlayAlpha
|
||
if is_instance_valid(_panel):
|
||
_panel.modulate.a = panelAlpha
|
||
|
||
func _releaseMovementInputState() -> void:
|
||
Input.flush_buffered_events()
|
||
_releaseMovementActions()
|
||
|
||
func _releaseMovementActions() -> void:
|
||
for action in MOVEMENT_ACTIONS:
|
||
Input.action_release(action)
|
||
|
||
func _clearChildren(node: Node) -> void:
|
||
for child in node.get_children():
|
||
child.queue_free()
|
||
|
||
func _shortTitle(title: String) -> String:
|
||
var trimmed := title.strip_edges()
|
||
if trimmed.length() <= 8:
|
||
return trimmed
|
||
return trimmed.substr(0, 8)
|
||
|
||
func _formatViews(views: int) -> String:
|
||
if views >= 10000:
|
||
var wan := float(views) / 10000.0
|
||
return "%.1f万看过" % wan
|
||
return "%d人看过" % views
|
||
|
||
func _createPanelStyle(color: Color, radius: int, shadow: bool) -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = color
|
||
style.corner_radius_top_left = radius
|
||
style.corner_radius_top_right = radius
|
||
style.corner_radius_bottom_left = radius
|
||
style.corner_radius_bottom_right = radius
|
||
style.content_margin_left = 10
|
||
style.content_margin_top = 7
|
||
style.content_margin_right = 10
|
||
style.content_margin_bottom = 7
|
||
if shadow:
|
||
style.shadow_color = Color(0.030, 0.120, 0.250, 0.18)
|
||
style.shadow_size = 16
|
||
style.shadow_offset = Vector2(0, 5)
|
||
return style
|
||
|
||
func _createButtonStyle(color: Color) -> StyleBoxFlat:
|
||
var style := _createPanelStyle(color, 18, false)
|
||
style.content_margin_left = 0
|
||
style.content_margin_top = 0
|
||
style.content_margin_right = 0
|
||
style.content_margin_bottom = 0
|
||
return style
|
||
|
||
func _createCardStyle(hover: bool) -> StyleBoxFlat:
|
||
var style := _createPanelStyle(Color.WHITE if not hover else Color(0.986, 0.996, 1.0, 1.0), 18, false)
|
||
style.border_width_left = 2
|
||
style.border_width_top = 2
|
||
style.border_width_right = 2
|
||
style.border_width_bottom = 2
|
||
style.border_color = Color(0.815, 0.890, 0.960, 0.92) if not hover else Color(0.000, 0.322, 0.851, 0.72)
|
||
style.shadow_color = Color(0.040, 0.150, 0.300, 0.08 if not hover else 0.14)
|
||
style.shadow_size = 4 if not hover else 8
|
||
style.shadow_offset = Vector2(0, 3)
|
||
return style
|