Refactor OpacitySlider

This commit is contained in:
1024jp 2024-02-18 02:18:41 +09:00
parent 3cccfd040b
commit 6cf3b15ac4
3 changed files with 144 additions and 91 deletions

View File

@ -633,6 +633,8 @@
2ACC65321C98033D000574DC /* ThemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACC65311C98033D000574DC /* ThemeTests.swift */; };
2ACD02BD22A87EFD00893051 /* ColorCode in Frameworks */ = {isa = PBXBuildFile; productRef = 2ACD02BC22A87EFD00893051 /* ColorCode */; };
2ACD02BF22A87F0400893051 /* ColorCode in Frameworks */ = {isa = PBXBuildFile; productRef = 2ACD02BE22A87F0400893051 /* ColorCode */; };
2ACDA2502B81201A00B2EBA8 /* OpacitySlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDA24F2B81201A00B2EBA8 /* OpacitySlider.swift */; };
2ACDA2512B81201A00B2EBA8 /* OpacitySlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDA24F2B81201A00B2EBA8 /* OpacitySlider.swift */; };
2ACDC0911D1726BD009B72D6 /* DotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDC0901D1726BD009B72D6 /* DotView.swift */; };
2ACDC0921D1726BD009B72D6 /* DotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDC0901D1726BD009B72D6 /* DotView.swift */; };
2ACDC0971D172B2A009B72D6 /* PaddingTextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDC0961D172B2A009B72D6 /* PaddingTextFieldCell.swift */; };
@ -1193,6 +1195,7 @@
2ACC21B41E52B8C50078241F /* DefaultKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultKeys.swift; sourceTree = "<group>"; };
2ACC5E401E7B08D300109ABC /* MultipleReplaceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleReplaceViewController.swift; sourceTree = "<group>"; };
2ACC65311C98033D000574DC /* ThemeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeTests.swift; sourceTree = "<group>"; };
2ACDA24F2B81201A00B2EBA8 /* OpacitySlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpacitySlider.swift; sourceTree = "<group>"; };
2ACDC0901D1726BD009B72D6 /* DotView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotView.swift; sourceTree = "<group>"; };
2ACDC0961D172B2A009B72D6 /* PaddingTextFieldCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaddingTextFieldCell.swift; sourceTree = "<group>"; };
2ACDC0991D172CDE009B72D6 /* AntialiasingTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AntialiasingTextField.swift; sourceTree = "<group>"; };
@ -2232,6 +2235,7 @@
2A59B7022957089A0094F03B /* LinkButton.swift */,
2A4A7D122856FF340085D2E7 /* HelpButton.swift */,
2A2615882977FCF6008C2240 /* SubmitButtonGroup.swift */,
2ACDA24F2B81201A00B2EBA8 /* OpacitySlider.swift */,
2A5D13121D1EE8FF00D38E6A /* HUDView.swift */,
2AA175F92AC5634500F6462C /* PopoverHolderView.swift */,
2AE144C32B0222DB005E8CF1 /* LiveTextInsertionView.swift */,
@ -2891,6 +2895,7 @@
2AD8D74B2064AD83000BEFDB /* NumberTextField.swift in Sources */,
2AC3845420C929950003F213 /* OpacitySampleView.swift in Sources */,
2A55D5EA2B7A86190092DE48 /* IssueReport.swift in Sources */,
2ACDA2502B81201A00B2EBA8 /* OpacitySlider.swift in Sources */,
2A158C222945F54B000A4EC1 /* OpacityView.swift in Sources */,
2AC6069C20416ADE00F9C839 /* OpenPanelAccessory.swift in Sources */,
2A3E61BF27C3795B00C6E5B6 /* OptionalMenu.swift in Sources */,
@ -3253,6 +3258,7 @@
2AD8D74A2064AD83000BEFDB /* NumberTextField.swift in Sources */,
2AC3845320C929950003F213 /* OpacitySampleView.swift in Sources */,
2A158C232945F54B000A4EC1 /* OpacityView.swift in Sources */,
2ACDA2512B81201A00B2EBA8 /* OpacitySlider.swift in Sources */,
2AC6069B20416ADE00F9C839 /* OpenPanelAccessory.swift in Sources */,
2A3E61C027C3795B00C6E5B6 /* OptionalMenu.swift in Sources */,
2A88E7711E81A2C7000019C6 /* OrderedSet.swift in Sources */,

View File

@ -0,0 +1,138 @@
//
// OpacitySlider.swift
//
// CotEditor
// https://coteditor.com
//
// Created by 1024jp on 2024-02-18.
//
// ---------------------------------------------------------------------------
//
// © 2024 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
struct OpacitySlider: View {
@Binding var value: Double
var bounds: ClosedRange<Double> = 0.2...1
var step: Double?
var body: some View {
if let step {
Slider(value: $value, in: self.bounds, step: step) {
EmptyView()
} minimumValueLabel: {
OpacitySample(opacity: self.bounds.lowerBound)
.help(self.minimumHelp)
} maximumValueLabel: {
OpacitySample(opacity: self.bounds.upperBound)
.help(self.maximumHelp)
}
} else {
Slider(value: $value, in: self.bounds) {
EmptyView()
} minimumValueLabel: {
OpacitySample(opacity: self.bounds.lowerBound)
.help(self.minimumHelp)
} maximumValueLabel: {
OpacitySample(opacity: self.bounds.upperBound)
.help(self.maximumHelp)
}
}
}
private var minimumHelp: String {
String(localized: "Transparent", comment: "tooltip for opacity slider")
}
private var maximumHelp: String {
String(localized: "Opaque", comment: "tooltip for opacity slider")
}
}
private struct OpacitySample: View {
let opacity: Double
private let inset: Double = 3
var body: some View {
GeometryReader { geometry in
let radius = geometry.size.height / 4
ZStack {
RoundedRectangle(cornerRadius: radius, style: .continuous)
.fill(.background)
Triangle()
.fill(.primary)
.opacity(1 - self.opacity)
.clipShape(RoundedRectangle(cornerRadius: radius, style: .continuous)
.inset(by: self.inset))
RoundedRectangle(cornerRadius: radius, style: .continuous)
.inset(by: 0.5)
.stroke(.tertiary, lineWidth: 1)
}
}
.aspectRatio(1, contentMode: .fit)
.frame(height: 16)
}
private struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.closeSubpath()
}
}
}
}
// MARK: - Preview
@available(macOS 14, *)
#Preview(traits: .fixedLayout(width: 200, height: 50)) {
VStack {
OpacitySlider(value: .constant(0.6))
OpacitySlider(value: .constant(0.6), step: 0.2)
}
.padding()
}
#Preview("OpacitySample") {
OpacitySample(opacity: 0.5)
.frame(width: 16, height: 16)
}

View File

@ -58,7 +58,6 @@ import SwiftUI
}
struct OpacityView: View {
weak var window: DocumentWindow?
@ -72,7 +71,6 @@ struct OpacityView: View {
Text("Editors Opacity")
.fontWeight(.semibold)
.foregroundStyle(.secondary)
.labelsHidden()
OpacitySlider(value: $opacity)
.onChange(of: self.opacity) { newValue in
@ -93,97 +91,8 @@ struct OpacityView: View {
private struct OpacitySlider: View {
@Binding private var value: Double
private let bounds: ClosedRange<Double>
private let label: LocalizedStringKey?
init(_ label: LocalizedStringKey? = nil, value: Binding<Double>, in bounds: ClosedRange<Double> = 0.2...1) {
self._value = value
self.bounds = bounds
self.label = label
}
var body: some View {
Slider(value: $value, in: self.bounds) {
if let label {
Text(label)
} else {
EmptyView()
}
} minimumValueLabel: {
OpacitySample(opacity: self.bounds.lowerBound)
.help("Transparent")
.frame(width: 16, height: 16)
} maximumValueLabel: {
OpacitySample(opacity: self.bounds.upperBound)
.help("Opaque")
.frame(width: 16, height: 16)
}
}
}
private struct OpacitySample: View {
let opacity: Double
private let inset: Double = 3
var body: some View {
GeometryReader { geometry in
let radius = geometry.size.height / 4
ZStack {
RoundedRectangle(cornerRadius: radius, style: .continuous)
.fill(.background)
Triangle()
.fill(.primary)
.opacity(1 - self.opacity)
.clipShape(RoundedRectangle(cornerRadius: radius, style: .continuous)
.inset(by: self.inset))
RoundedRectangle(cornerRadius: radius, style: .continuous)
.inset(by: 0.5)
.stroke(.tertiary, lineWidth: 1)
}
}
}
private struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.closeSubpath()
}
}
}
}
// MARK: - Preview
#Preview {
OpacityView()
}
#Preview("OpacitySample") {
OpacitySample(opacity: 0.5)
.frame(width: 16, height: 16)
}