mirror of
https://github.com/exyte/Macaw.git
synced 2024-11-13 05:07:24 +03:00
Add NodeLayout
This commit is contained in:
parent
f035a6c8d5
commit
e2c4d9650d
@ -7,9 +7,9 @@
|
||||
|
||||
class SVGCanvas: Group {
|
||||
|
||||
let layout: ContentLayout
|
||||
let layout: NodeLayout
|
||||
|
||||
public init(layout: ContentLayout, contents: [Node] = []) {
|
||||
public init(layout: NodeLayout, contents: [Node] = []) {
|
||||
self.layout = layout
|
||||
super.init(contents: contents)
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ open class SVGParser {
|
||||
fileprivate func parse() -> Group {
|
||||
let parsedXml = SWXMLHash.parse(xmlString)
|
||||
|
||||
var layout: ContentLayout?
|
||||
var layout: NodeLayout?
|
||||
for child in parsedXml.children {
|
||||
if let element = child.element {
|
||||
if element.name == "svg" {
|
||||
@ -119,7 +119,7 @@ open class SVGParser {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func parseViewBox(_ element: SWXMLHash.XMLElement) -> SvgContentLayout? {
|
||||
fileprivate func parseViewBox(_ element: SWXMLHash.XMLElement) -> SvgNodeLayout? {
|
||||
var svgDimensions: Dimensions?
|
||||
if let w = getDimensionValue(element, attribute: "width"), let h = getDimensionValue(element, attribute: "height") {
|
||||
svgDimensions = Dimensions(width: w, height: h)
|
||||
@ -143,7 +143,7 @@ open class SVGParser {
|
||||
let strings = contentModeString.components(separatedBy: CharacterSet(charactersIn: " "))
|
||||
if strings.count == 1 { // none
|
||||
scalingMode = parseAspectRatio(strings[0])
|
||||
return SvgContentLayout(svgDimensions: svgDimensions, viewBox: viewBox, scalingMode: scalingMode)
|
||||
return SvgNodeLayout(svgDimensions: svgDimensions, viewBox: viewBox, scalingMode: scalingMode)
|
||||
}
|
||||
guard strings.count == 2 else { fatalError("Invalid content mode") }
|
||||
|
||||
@ -159,7 +159,7 @@ open class SVGParser {
|
||||
scalingMode = parseAspectRatio(strings[1])
|
||||
}
|
||||
|
||||
return SvgContentLayout(svgDimensions: svgDimensions, viewBox: viewBox, scalingMode: scalingMode, xAligningMode: xAligningMode, yAligningMode: yAligningMode)
|
||||
return SvgNodeLayout(svgDimensions: svgDimensions, viewBox: viewBox, scalingMode: scalingMode, xAligningMode: xAligningMode, yAligningMode: yAligningMode)
|
||||
}
|
||||
|
||||
fileprivate func parseNode(_ node: XMLIndexer, groupStyle: [String: String] = [:]) -> Node? {
|
||||
|
@ -1,27 +1,4 @@
|
||||
|
||||
enum Dimension {
|
||||
case percent(Double)
|
||||
case pixels(Double)
|
||||
|
||||
init(percent: Double) {
|
||||
self = .percent(percent)
|
||||
}
|
||||
|
||||
init(pixels: Double) {
|
||||
self = .pixels(pixels)
|
||||
}
|
||||
}
|
||||
|
||||
class Dimensions {
|
||||
let width: Dimension
|
||||
let height: Dimension
|
||||
|
||||
public init(width: Dimension, height: Dimension) {
|
||||
self.width = width
|
||||
self.height = height
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ContentLayout {
|
||||
|
||||
static var standard: ContentLayout { get }
|
||||
@ -30,24 +7,11 @@ public protocol ContentLayout {
|
||||
|
||||
class SvgContentLayout: ContentLayout {
|
||||
|
||||
public let scalingMode: AspectRatio
|
||||
public let xAligningMode: Align
|
||||
public let yAligningMode: Align
|
||||
let scalingMode: AspectRatio
|
||||
let xAligningMode: Align
|
||||
let yAligningMode: Align
|
||||
|
||||
let svgDimensions: Dimensions?
|
||||
let viewBox: Rect?
|
||||
|
||||
public init(scalingMode: AspectRatio? = .meet, xAligningMode: Align? = .mid, yAligningMode: Align? = .mid) {
|
||||
self.svgDimensions = .none
|
||||
self.viewBox = .none
|
||||
self.scalingMode = scalingMode ?? .meet
|
||||
self.xAligningMode = xAligningMode ?? .mid
|
||||
self.yAligningMode = yAligningMode ?? .mid
|
||||
}
|
||||
|
||||
init(svgDimensions: Dimensions? = .none, viewBox: Rect? = .none, scalingMode: AspectRatio? = .meet, xAligningMode: Align? = .mid, yAligningMode: Align? = .mid) {
|
||||
self.svgDimensions = svgDimensions
|
||||
self.viewBox = viewBox
|
||||
init(scalingMode: AspectRatio? = .meet, xAligningMode: Align? = .mid, yAligningMode: Align? = .mid) {
|
||||
self.scalingMode = scalingMode ?? .meet
|
||||
self.xAligningMode = xAligningMode ?? .mid
|
||||
self.yAligningMode = yAligningMode ?? .mid
|
||||
|
88
Source/utils/SvgNodeLayout.swift
Normal file
88
Source/utils/SvgNodeLayout.swift
Normal file
@ -0,0 +1,88 @@
|
||||
|
||||
enum Dimension {
|
||||
case percent(Double)
|
||||
case pixels(Double)
|
||||
|
||||
init(percent: Double) {
|
||||
self = .percent(percent)
|
||||
}
|
||||
|
||||
init(pixels: Double) {
|
||||
self = .pixels(pixels)
|
||||
}
|
||||
}
|
||||
|
||||
class Dimensions {
|
||||
let width: Dimension
|
||||
let height: Dimension
|
||||
|
||||
public init(width: Dimension, height: Dimension) {
|
||||
self.width = width
|
||||
self.height = height
|
||||
}
|
||||
}
|
||||
|
||||
public protocol NodeLayout {
|
||||
|
||||
func layout(in rect: Rect) -> (Transform, Locus?)
|
||||
}
|
||||
|
||||
class SvgNodeLayout: NodeLayout {
|
||||
|
||||
let svgDimensions: Dimensions?
|
||||
let viewBox: Rect?
|
||||
let scalingMode: AspectRatio
|
||||
let xAligningMode: Align
|
||||
let yAligningMode: Align
|
||||
|
||||
init(svgDimensions: Dimensions? = .none, viewBox: Rect? = .none, scalingMode: AspectRatio? = .meet, xAligningMode: Align? = .mid, yAligningMode: Align? = .mid) {
|
||||
self.svgDimensions = svgDimensions
|
||||
self.viewBox = viewBox
|
||||
self.scalingMode = scalingMode ?? .meet
|
||||
self.xAligningMode = xAligningMode ?? .mid
|
||||
self.yAligningMode = yAligningMode ?? .mid
|
||||
}
|
||||
|
||||
public func layout(in rect: Rect) -> (Transform, Locus?) {
|
||||
|
||||
var clip: Locus? = .none
|
||||
var transform = Transform.identity
|
||||
|
||||
guard let dimensions = svgDimensions else {
|
||||
return (transform, clip)
|
||||
}
|
||||
|
||||
let width = dimensionToPixels(dimensions.width, framePixels: rect.w)
|
||||
let height = dimensionToPixels(dimensions.height, framePixels: rect.h)
|
||||
let svgSize = Size(w: width, h: height)
|
||||
|
||||
if let viewBox = self.viewBox {
|
||||
clip = viewBox
|
||||
}
|
||||
let viewBox = self.viewBox ?? Rect(x: 0, y: 0, w: svgSize.w, h: svgSize.h)
|
||||
|
||||
if scalingMode === AspectRatio.slice {
|
||||
// setup new clipping to slice extra bits
|
||||
let newSize = AspectRatio.meet.fit(size: svgSize, into: viewBox)
|
||||
let newX = viewBox.x + xAligningMode.align(outer: viewBox.w, inner: newSize.w)
|
||||
let newY = viewBox.y + yAligningMode.align(outer: viewBox.h, inner: newSize.h)
|
||||
clip = Rect(x: newX, y: newY, w: newSize.w, h: newSize.h)
|
||||
}
|
||||
|
||||
let contentLayout = SvgContentLayout(scalingMode: scalingMode, xAligningMode: xAligningMode, yAligningMode: yAligningMode)
|
||||
transform = contentLayout.layout(rect: viewBox, into: Rect(x: 0, y: 0, w: svgSize.w, h: svgSize.h))
|
||||
|
||||
// move to (0, 0)
|
||||
transform = transform.move(dx: -viewBox.x, dy: -viewBox.y)
|
||||
return (transform, clip)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func dimensionToPixels(_ dimension: Dimension, framePixels: Double) -> Double {
|
||||
switch(dimension) {
|
||||
case let .percent(percent):
|
||||
return framePixels * percent / 100.0
|
||||
case let .pixels(pixels):
|
||||
return pixels
|
||||
}
|
||||
}
|
@ -18,31 +18,8 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
didSet {
|
||||
if let canvas = node as? SVGCanvas, let params = canvas.layout as? SvgContentLayout, let dimensions = params.svgDimensions {
|
||||
|
||||
let width = dimensionToPixels(dimensions.width, framePixels: Double(frame.width))
|
||||
let height = dimensionToPixels(dimensions.height, framePixels: Double(frame.height))
|
||||
let svgSize = Size(w: width, h: height)
|
||||
|
||||
let scalingMode = params.scalingMode
|
||||
if let viewBox = params.viewBox {
|
||||
canvas.clip = viewBox
|
||||
}
|
||||
let viewBox = params.viewBox ?? Rect(x: 0, y: 0, w: svgSize.w, h: svgSize.h)
|
||||
|
||||
if scalingMode === AspectRatio.slice {
|
||||
// setup new clipping to slice extra bits
|
||||
let newSize = AspectRatio.meet.fit(size: svgSize, into: viewBox)
|
||||
let newX = viewBox.x + params.xAligningMode.align(outer: viewBox.w, inner: newSize.w)
|
||||
let newY = viewBox.y + params.yAligningMode.align(outer: viewBox.h, inner: newSize.h)
|
||||
node.clip = Rect(x: newX, y: newY, w: newSize.w, h: newSize.h)
|
||||
}
|
||||
|
||||
let contentLayout = SvgContentLayout(scalingMode: scalingMode, xAligningMode: params.xAligningMode, yAligningMode: params.yAligningMode)
|
||||
node.place = contentLayout.layout(rect: viewBox, into: Rect(x: 0, y: 0, w: svgSize.w, h: svgSize.h))
|
||||
|
||||
// move to (0, 0)
|
||||
node.place = node.place.move(dx: -viewBox.x, dy: -viewBox.y)
|
||||
if let canvas = node as? SVGCanvas, let layout = canvas.layout as? SvgNodeLayout {
|
||||
(canvas.place, canvas.clip) = layout.layout(in: Rect(cgRect: bounds))
|
||||
}
|
||||
|
||||
nodesMap.add(node, view: self)
|
||||
@ -200,15 +177,6 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
guard let ctx = context.cgContext else { return .none }
|
||||
return renderer?.findNodeAt(location: location, ctx: ctx)
|
||||
}
|
||||
|
||||
fileprivate func dimensionToPixels(_ dimension: Dimension, framePixels: Double) -> Double {
|
||||
switch(dimension) {
|
||||
case let .percent(percent):
|
||||
return framePixels * percent / 100.0
|
||||
case let .pixels(pixels):
|
||||
return pixels
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Touches
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user