refactor: restore Bricks .ui for video player + native overlay controls

- miniplayer.ui: VideoPlayer 100% fullscreen (Bricks renders video)
- BricksAppView: loads .ui via BricksView, ZStack overlays native controls
- ControlToolbar: semi-transparent (0.6 opacity), slides from bottom
- 30-second auto-hide timer with mouse-hover reset
- Left-top icon toggles toolbar visibility
This commit is contained in:
yumoqing 2026-06-23 08:36:27 +08:00
parent 20f4dec0b9
commit ad1be14a0c
2 changed files with 69 additions and 170 deletions

View File

@ -131,36 +131,82 @@ struct BricksAppView: View {
@ObservedObject var engine: BricksEngine
@State private var showToolbar = false
@State private var isHoveringIcon = false
@State private var hideTimer: Timer?
@State private var isHoveringToolbar = false
@State private var jsonString: String?
private func resetHideTimer() {
hideTimer?.invalidate()
guard showToolbar else { return }
hideTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: false) { _ in
withAnimation(.easeInOut(duration: 0.5)) {
showToolbar = false
}
}
}
var body: some View {
ZStack(alignment: .topLeading) {
//
VideoPlayerRepresentable(player: bridge.player)
.ignoresSafeArea()
// Bricks
if let json = jsonString {
BricksView(json: json, engine: engine)
} else {
ProgressView("Loading UI...")
.task {
jsonString = loadJSONString()
}
}
//
MiniPlayerIcon()
.frame(width: 32, height: 32)
.padding(12)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(Color.black.opacity(0.3))
)
.opacity(isHoveringIcon ? 1.0 : 0.6)
.onHover { isHoveringIcon = $0 }
.onTapGesture {
withAnimation(.easeInOut(duration: 0.25)) {
showToolbar.toggle()
.contentShape(Rectangle())
.highPriorityGesture(
TapGesture().onEnded {
withAnimation(.easeInOut(duration: 0.25)) {
showToolbar.toggle()
}
resetHideTimer()
}
}
)
// /
// 30
VStack {
Spacer()
if showToolbar {
ControlToolbar(bridge: bridge, isHovering: .constant(false)) {
// onTouch:
ControlToolbar(bridge: bridge, isHovering: $isHoveringToolbar) {
resetHideTimer()
}
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color.black.opacity(0.6))
)
.padding(12)
.onHover { hovering in
isHoveringToolbar = hovering
if hovering { resetHideTimer() }
}
.transition(.move(edge: .bottom).combined(with: .opacity))
}
}
}
.onChange(of: showToolbar) { _, newValue in
if newValue {
resetHideTimer()
} else {
hideTimer?.invalidate()
}
}
.onDisappear {
hideTimer?.invalidate()
}
.sheet(isPresented: $bridge.showURLDialog) {
URLInputDialog(bridge: bridge)
}
@ -171,6 +217,15 @@ struct BricksAppView: View {
PlaylistWindowView(bridge: bridge)
}
}
private func loadJSONString() -> String {
if let url = Bundle.module.url(forResource: "miniplayer", withExtension: "ui"),
let data = try? Data(contentsOf: url),
let str = String(data: data, encoding: .utf8) {
return str
}
return "{\"widgettype\":\"VideoPlayer\",\"id\":\"videoPlayer\",\"options\":{\"width\":\"100%\",\"height\":\"100%\"}}"
}
}

View File

@ -1,164 +1,8 @@
{
"widgettype": "VBox",
"widgettype": "VideoPlayer",
"id": "videoPlayer",
"options": {
"width": "100%",
"height": "100%",
"bgcolor": "#000000"
},
"subwidgets": [
{
"widgettype": "VideoPlayer",
"id": "videoPlayer",
"options": {
"width": "100%",
"height": "85%"
}
},
{
"widgettype": "HBox",
"options": {
"width": "100%",
"height": "15%",
"bgcolor": "#1a1a1a",
"spacing": 6,
"padding": "8px 12px"
},
"subwidgets": [
{
"widgettype": "Button",
"id": "openBtn",
"options": {
"label": "open",
"icon": "folder",
"cwidth": 8,
"cheight": 4,
"bgcolor": "#555555",
"color": "#ffffff"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "event",
"target": "videoPlayer.openFile"
}
]
},
{
"widgettype": "Button",
"id": "prevBtn",
"options": {
"label": "prev",
"icon": "backward.fill",
"cwidth": 8,
"cheight": 4,
"bgcolor": "#333333",
"color": "#ffffff"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "event",
"target": "videoPlayer.prev"
}
]
},
{
"widgettype": "Button",
"id": "toggleBtn",
"options": {
"label": "play_pause",
"icon": "playpause.fill",
"cwidth": 12,
"cheight": 4,
"bgcolor": "#007aff",
"color": "#ffffff"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "event",
"target": "videoPlayer.toggle"
}
]
},
{
"widgettype": "Button",
"id": "nextBtn",
"options": {
"label": "next",
"icon": "forward.fill",
"cwidth": 8,
"cheight": 4,
"bgcolor": "#333333",
"color": "#ffffff"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "event",
"target": "videoPlayer.next"
}
]
},
{
"widgettype": "Filler"
},
{
"widgettype": "Input",
"id": "videoUrlInput",
"options": {
"name": "url",
"placeholder": "url_placeholder",
"cwidth": 30,
"cheight": 4
}
},
{
"widgettype": "Button",
"id": "loadBtn",
"options": {
"label": "load",
"icon": "arrow.down.circle.fill",
"cwidth": 8,
"cheight": 4,
"bgcolor": "#5856d6",
"color": "#ffffff"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "method",
"target": "videoPlayer",
"method": "setURL"
}
]
},
{
"widgettype": "Button",
"id": "fullscreenBtn",
"options": {
"label": "fullscreen",
"icon": "arrow.up.left.and.arrow.down.right",
"cwidth": 10,
"cheight": 4,
"bgcolor": "#ff9500",
"color": "#ffffff"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "event",
"target": "videoPlayer.fullscreen"
}
]
}
]
}
]
"height": "100%"
}
}