mirror of
https://github.com/HaishinKit/HaishinKit.swift.git
synced 2026-05-07 20:12:28 +00:00
198 lines
5.9 KiB
Swift
198 lines
5.9 KiB
Swift
import Accelerate
|
||
import AVFoundation
|
||
import CoreImage
|
||
import CoreMedia
|
||
import Foundation
|
||
import VideoToolbox
|
||
|
||
#if canImport(AppKit)
|
||
import AppKit
|
||
#endif
|
||
|
||
#if canImport(UIKit)
|
||
import UIKit
|
||
#endif
|
||
|
||
@ScreenActor
|
||
protocol ScreenObjectConvertible {
|
||
static var type: String { get }
|
||
}
|
||
|
||
extension ScreenObjectConvertible {
|
||
static var type: String { "object" }
|
||
}
|
||
|
||
// The ScreenObject class is the abstract class for all objects that are rendered on the screen.
|
||
@ScreenActor
|
||
open class ScreenObject: ScreenObjectConvertible {
|
||
/// The horizontal alignment for the screen object.
|
||
public enum HorizontalAlignment: Int, Sendable {
|
||
/// A guide that marks the left edge of the screen object.
|
||
case left = 0
|
||
/// A guide that marks the borizontal center of the screen object.
|
||
case center = 1
|
||
/// A guide that marks the right edge of the screen object.
|
||
case right = 2
|
||
}
|
||
|
||
/// The vertical alignment for the screen object.
|
||
public enum VerticalAlignment: Int, Sendable {
|
||
/// A guide that marks the top edge of the screen object.
|
||
case top = 0
|
||
/// A guide that marks the vertical middle of the screen object.
|
||
case middle = 1
|
||
/// A guide that marks the bottom edge of the screen object.
|
||
case bottom = 2
|
||
}
|
||
|
||
enum BlendMode {
|
||
case normal
|
||
case alpha
|
||
}
|
||
|
||
/// The screen object container that contains this screen object
|
||
public internal(set) weak var parent: ScreenObjectContainer?
|
||
|
||
/// Specifies the size rectangle.
|
||
public var size: CGSize = .zero {
|
||
didSet {
|
||
guard size != oldValue else {
|
||
return
|
||
}
|
||
shouldInvalidateLayout = true
|
||
}
|
||
}
|
||
|
||
/// Unique identifier of this screen object.
|
||
///
|
||
/// The identifier must be unique within the owning scene or document
|
||
/// and is commonly used for lookup and state management.
|
||
public let id: String
|
||
|
||
/// The bounds rectangle.
|
||
public internal(set) var bounds: CGRect = .zero
|
||
|
||
/// Specifies the visibility of the object.
|
||
public var isVisible = true
|
||
|
||
#if os(macOS)
|
||
/// Specifies the default spacing to laying out content in the screen object.
|
||
public var layoutMargin: NSEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: 0)
|
||
#else
|
||
/// Specifies the default spacing to laying out content in the screen object.
|
||
public var layoutMargin: UIEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: 0)
|
||
#endif
|
||
|
||
/// Specifies the radius to use when drawing rounded corners.
|
||
public var cornerRadius: CGFloat = 0.0
|
||
|
||
/// Specifies the alignment position along the vertical axis.
|
||
public var verticalAlignment: VerticalAlignment = .top
|
||
|
||
/// Specifies the alignment position along the horizontal axis.
|
||
public var horizontalAlignment: HorizontalAlignment = .left
|
||
|
||
public var elements: [String: String] {
|
||
get {
|
||
return [:]
|
||
}
|
||
set(value) {
|
||
}
|
||
}
|
||
|
||
var blendMode: BlendMode {
|
||
.alpha
|
||
}
|
||
|
||
var shouldInvalidateLayout = true
|
||
|
||
/// Creates a screen object.
|
||
public init(id: String? = nil) {
|
||
self.id = id ?? UUID().uuidString
|
||
}
|
||
|
||
/// Invalidates the current layout and triggers a layout update.
|
||
public func invalidateLayout() {
|
||
shouldInvalidateLayout = true
|
||
}
|
||
|
||
/// Makes ciImage for offscreen image.
|
||
open func makeImage(_ renderer: some ScreenRenderer) -> CIImage? {
|
||
return nil
|
||
}
|
||
|
||
/// Finds a screen object with the specified identifier.
|
||
///
|
||
/// This method compares the given identifier with the receiver’s identifier
|
||
/// and returns the receiver itself if they match.
|
||
/// Subclasses may override this method to provide recursive or
|
||
/// hierarchical lookup behavior.
|
||
///
|
||
/// - Parameter id: The unique identifier of the screen object to find.
|
||
/// - Returns: The screen object whose identifier matches the given value,
|
||
/// or `nil` if no match is found.
|
||
open func findById(_ id: String) -> ScreenObject? {
|
||
if self.id == id {
|
||
return self
|
||
}
|
||
return nil
|
||
}
|
||
|
||
/// Makes screen object bounds for offscreen image.
|
||
open func makeBounds(_ size: CGSize) -> CGRect {
|
||
guard let parent else {
|
||
return .init(origin: .zero, size: self.size)
|
||
}
|
||
|
||
let width = size.width == 0 ? max(parent.bounds.width - layoutMargin.left - layoutMargin.right + size.width, 0) : size.width
|
||
let height = size.height == 0 ? max(parent.bounds.height - layoutMargin.top - layoutMargin.bottom + size.height, 0) : size.height
|
||
|
||
let parentX = parent.bounds.origin.x
|
||
let parentWidth = parent.bounds.width
|
||
let x: CGFloat
|
||
switch horizontalAlignment {
|
||
case .center:
|
||
x = parentX + (parentWidth - width) / 2
|
||
case .left:
|
||
x = parentX + layoutMargin.left
|
||
case .right:
|
||
x = parentX + (parentWidth - width) - layoutMargin.right
|
||
}
|
||
|
||
let parentY = parent.bounds.origin.y
|
||
let parentHeight = parent.bounds.height
|
||
let y: CGFloat
|
||
switch verticalAlignment {
|
||
case .top:
|
||
y = parentY + layoutMargin.top
|
||
case .middle:
|
||
y = parentY + (parentHeight - height) / 2
|
||
case .bottom:
|
||
y = parentY + (parentHeight - height) - layoutMargin.bottom
|
||
}
|
||
|
||
return .init(x: x, y: y, width: width, height: height)
|
||
}
|
||
|
||
func layout(_ renderer: some ScreenRenderer) {
|
||
bounds = makeBounds(size)
|
||
renderer.layout(self)
|
||
shouldInvalidateLayout = false
|
||
}
|
||
|
||
func draw(_ renderer: some ScreenRenderer) {
|
||
renderer.draw(self)
|
||
}
|
||
}
|
||
|
||
extension ScreenObject: Hashable {
|
||
// MARK: Hashable
|
||
nonisolated public static func == (lhs: ScreenObject, rhs: ScreenObject) -> Bool {
|
||
lhs === rhs
|
||
}
|
||
|
||
nonisolated public func hash(into hasher: inout Hasher) {
|
||
hasher.combine(ObjectIdentifier(self))
|
||
}
|
||
}
|