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:
parent
20f4dec0b9
commit
ad1be14a0c
@ -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%\"}}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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%"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user