-
-
Notifications
You must be signed in to change notification settings - Fork 7
Custom note views #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,12 +9,62 @@ public struct PianoRollDemoView: View { | |
| @State var model = PianoRollModel(notes: [ | ||
| PianoRollNote(start: 1, length: 2, pitch: 3), | ||
| PianoRollNote(start: 5, length: 1, pitch: 4), | ||
| ], length: 128, height: 128) | ||
| ], length: 16, height: 16) | ||
|
|
||
| static let pitchColors: [Color] = [ | ||
| .red, .orange, .yellow, .green, .mint, .cyan, | ||
| .blue, .indigo, .purple, .pink, .red, .orange, | ||
| .yellow, .green, .mint, .cyan, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) |
||
| ] | ||
|
|
||
| @State var customModel = PianoRollModel(notes: [ | ||
| PianoRollNote(start: 0, length: 2, pitch: 3, text: "C"), | ||
| PianoRollNote(start: 2, length: 2, pitch: 5, text: "E"), | ||
| PianoRollNote(start: 4, length: 3, pitch: 7, text: "G"), | ||
| PianoRollNote(start: 4, length: 1, pitch: 10, text: "B♭"), | ||
| PianoRollNote(start: 7, length: 1, pitch: 8, text: "A♭"), | ||
| PianoRollNote(start: 8, length: 4, pitch: 5, text: "E"), | ||
| PianoRollNote(start: 8, length: 2, pitch: 12, text: "D"), | ||
| PianoRollNote(start: 10, length: 1, pitch: 9, text: "A"), | ||
| PianoRollNote(start: 12, length: 3, pitch: 6, text: "F"), | ||
| PianoRollNote(start: 12, length: 1, pitch: 14, text: "F"), | ||
| PianoRollNote(start: 13, length: 2, pitch: 11, text: "C♯"), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma) |
||
| ], length: 16, height: 16) | ||
|
|
||
| public var body: some View { | ||
| ScrollView([.horizontal, .vertical], showsIndicators: true) { | ||
| PianoRoll(model: $model, noteColor: .cyan, layout: .vertical) | ||
| }.background(Color(white: 0.1)) | ||
| VStack(spacing: 20) { | ||
| Text("Default Note Style") | ||
| ScrollView([.horizontal, .vertical], showsIndicators: true) { | ||
| PianoRoll(model: $model, noteColor: .cyan, layout: .horizontal) | ||
| }.background(Color(white: 0.4)) | ||
|
|
||
| Text("Custom Neon Pill Notes") | ||
| ScrollView([.horizontal, .vertical], showsIndicators: true) { | ||
| PianoRoll(model: $customModel, noteColor: .cyan, layout: .horizontal) { note, isActive in | ||
| let color = Self.pitchColors[(note.pitch - 1) % Self.pitchColors.count] | ||
| ZStack { | ||
| RoundedRectangle(cornerRadius: 12) | ||
| .fill( | ||
| LinearGradient( | ||
| colors: [color, color.opacity(0.6)], | ||
| startPoint: .top, | ||
| endPoint: .bottom | ||
| ) | ||
| ) | ||
| .opacity(isActive ? 1.0 : 0.85) | ||
| .shadow(color: color.opacity(isActive ? 0.9 : 0.5), radius: isActive ? 8 : 4) | ||
| RoundedRectangle(cornerRadius: 12) | ||
| .strokeBorder(.white.opacity(0.4), lineWidth: 1) | ||
| Text(note.text ?? "") | ||
| .font(.system(.caption, design: .rounded).bold()) | ||
| .foregroundColor(.white) | ||
| .shadow(color: .black.opacity(0.5), radius: 2, x: 0, y: 1) | ||
| } | ||
| .scaleEffect(isActive ? 1.05 : 1.0) | ||
| .animation(.easeOut(duration: 0.15), value: isActive) | ||
| } | ||
| }.background(Color(white: 0.12)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| // Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/PianoRoll/ | ||
|
|
||
| import SwiftUI | ||
|
|
||
| /// The default note appearance used by PianoRoll when no custom noteContent closure is provided. | ||
| public struct DefaultNoteView: View { | ||
| var note: PianoRollNote | ||
| var color: Color | ||
| var isActive: Bool | ||
| var lineOpacity: Double | ||
| var layout: PianoRollLayout | ||
|
|
||
| public var body: some View { | ||
| switch layout { | ||
| case .horizontal: | ||
| ZStack(alignment: .trailing) { | ||
| ZStack(alignment: .leading) { | ||
| Rectangle() | ||
| .foregroundColor((note.color ?? color).opacity(isActive ? 1.0 : 0.8)) | ||
| Text(note.text ?? "") | ||
| .opacity(note.text == nil ? 0 : 1) | ||
| .padding(.leading, 5) | ||
| } | ||
| Rectangle() | ||
| .foregroundColor(.black) | ||
| .padding(4) | ||
| .frame(width: 10) | ||
| .opacity(lineOpacity) | ||
| } | ||
| case .vertical: | ||
| ZStack(alignment: .bottom) { | ||
| ZStack(alignment: .bottom) { | ||
| Rectangle() | ||
| .foregroundColor((note.color ?? color).opacity(isActive ? 1.0 : 0.8)) | ||
| Text(note.text ?? "") | ||
| .opacity(note.text == nil ? 0 : 1) | ||
| .padding(.bottom, 5) | ||
| } | ||
| Rectangle() | ||
| .foregroundColor(.black) | ||
| .padding(4) | ||
| .frame(height: 10) | ||
| .opacity(lineOpacity) | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,39 +2,46 @@ | |
|
|
||
| import SwiftUI | ||
|
|
||
| /// Layout orientation for the piano roll. | ||
| public enum PianoRollLayout: Sendable { | ||
| case horizontal | ||
| case vertical | ||
| } | ||
|
|
||
| /// Touch-oriented piano roll. | ||
| /// | ||
| /// Note: Requires macOS 12 / iOS 15 due to SwiftUI bug (crashes in SwiftUI when deleting notes). | ||
| public struct PianoRoll: View { | ||
| public enum Layout: Sendable { | ||
| case horizontal | ||
| case vertical | ||
| } | ||
| public struct PianoRoll<NoteContent: View>: View { | ||
| public typealias Layout = PianoRollLayout | ||
|
|
||
| @Binding var model: PianoRollModel | ||
| var editable: Bool | ||
| var gridColor: Color | ||
| var gridSize: CGSize | ||
| var noteColor: Color | ||
| var noteLineOpacity: Double | ||
| var layout: Layout | ||
| var layout: PianoRollLayout | ||
| var noteContent: (PianoRollNote, Bool) -> NoteContent | ||
|
|
||
| /// Initialize PianoRoll with a binding to a model and a color | ||
| /// Initialize PianoRoll with a binding to a model, a color, and a custom note view builder | ||
| /// - Parameters: | ||
| /// - editable: Disable edition of any note in piano roll | ||
| /// - model: PianoRoll data | ||
| /// - noteColor: Color to use for the note indicator, defaults to system accent color | ||
| /// - noteLineOpacity: Opacity of the note view vertical black line | ||
| /// - gridColor: Color of grid | ||
| /// - gridSize: Size of a grid cell | ||
| /// - layout: Horizontal or vertical layout | ||
| /// - noteContent: Custom view builder for note appearance. Receives the note and whether it is active (hovering/dragging). | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line Length Violation: Line should be 120 characters or less: currently 129 characters (line_length) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line Length Violation: Line should be 120 characters or less: currently 129 characters (line_length) |
||
| public init( | ||
| editable: Bool = true, | ||
| model: Binding<PianoRollModel>, | ||
| noteColor: Color = .accentColor, | ||
| noteLineOpacity: Double = 1, | ||
| gridColor: Color = Color(red: 15.0 / 255.0, green: 17.0 / 255.0, blue: 16.0 / 255.0), | ||
| gridSize: CGSize = CGSize(width: 80, height: 40), | ||
| layout: Layout = .horizontal | ||
| layout: PianoRollLayout = .horizontal, | ||
| @ViewBuilder noteContent: @escaping (PianoRollNote, Bool) -> NoteContent | ||
| ) { | ||
| _model = model | ||
| self.noteColor = noteColor | ||
|
|
@@ -43,6 +50,7 @@ public struct PianoRoll: View { | |
| self.gridColor = gridColor | ||
| self.editable = editable | ||
| self.layout = layout | ||
| self.noteContent = noteContent | ||
| } | ||
|
|
||
| private var width: CGFloat { | ||
|
|
@@ -92,7 +100,7 @@ public struct PianoRoll: View { | |
| sequenceHeight: model.height, | ||
| isContinuous: true, | ||
| editable: editable, | ||
| lineOpacity: noteLineOpacity | ||
| noteContent: noteContent | ||
| ).onTapGesture { | ||
| guard editable else { return } | ||
| model.notes.removeAll(where: { $0 == note }) | ||
|
|
@@ -107,7 +115,7 @@ public struct PianoRoll: View { | |
| sequenceHeight: model.height, | ||
| isContinuous: true, | ||
| editable: editable, | ||
| lineOpacity: noteLineOpacity | ||
| noteContent: noteContent | ||
| ).onTapGesture { | ||
| guard editable else { return } | ||
| model.notes.removeAll(where: { $0 == note }) | ||
|
|
@@ -119,6 +127,37 @@ public struct PianoRoll: View { | |
| } | ||
| } | ||
|
|
||
| /// Backward-compatible initializer that uses the default note appearance. | ||
| extension PianoRoll where NoteContent == DefaultNoteView { | ||
| public init( | ||
| editable: Bool = true, | ||
| model: Binding<PianoRollModel>, | ||
| noteColor: Color = .accentColor, | ||
| noteLineOpacity: Double = 1, | ||
| gridColor: Color = Color(red: 15.0 / 255.0, green: 17.0 / 255.0, blue: 16.0 / 255.0), | ||
| gridSize: CGSize = CGSize(width: 80, height: 40), | ||
| layout: PianoRollLayout = .horizontal | ||
| ) { | ||
| self.init( | ||
| editable: editable, | ||
| model: model, | ||
| noteColor: noteColor, | ||
| noteLineOpacity: noteLineOpacity, | ||
| gridColor: gridColor, | ||
| gridSize: gridSize, | ||
| layout: layout | ||
| ) { note, isActive in | ||
| DefaultNoteView( | ||
| note: note, | ||
| color: noteColor, | ||
| isActive: isActive, | ||
| lineOpacity: editable ? noteLineOpacity : 0, | ||
| layout: layout | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| struct PianoRollPreview: View { | ||
| init() {} | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing Comma Violation: Collection literals should not have trailing commas. (trailing_comma)