feat: round embedded note images
This commit is contained in:
@@ -179,6 +179,8 @@ Typing, pasting, and Return at the visible start of an item use the position aft
|
|||||||
|
|
||||||
Fenced code uses [HighlighterSwift](https://github.com/smittytone/HighlighterSwift) (Highlight.js) for language-aware token colors. Add a language after the opening backticks or tildes, for example `py`, `python`, `swift`, `js`, or `json`. Unlabeled fences use automatic detection; unknown languages remain plain monospace. Highlighting runs entirely offline.
|
Fenced code uses [HighlighterSwift](https://github.com/smittytone/HighlighterSwift) (Highlight.js) for language-aware token colors. Add a language after the opening backticks or tildes, for example `py`, `python`, `swift`, `js`, or `json`. Unlabeled fences use automatic detection; unknown languages remain plain monospace. Highlighting runs entirely offline.
|
||||||
|
|
||||||
|
Embedded images display with 8-point rounded corners. Rounding only clips their rendering in Notes; saving and copying retain the original image data.
|
||||||
|
|
||||||
Notes autosave locally as RTFD packages, including embedded images, under `~/Library/Application Support/Conjure/Notes`. No account, network access, or cloud sync is involved. Save failures appear below the editor and prevent switching, closing, or quitting until saving succeeds, preserving the unsaved buffer. The Close button and Command-W dismiss Conjure, including any open popup; leaving Notes or switching destinations removes its popup completely and restores the launcher’s original dimensions.
|
Notes autosave locally as RTFD packages, including embedded images, under `~/Library/Application Support/Conjure/Notes`. No account, network access, or cloud sync is involved. Save failures appear below the editor and prevent switching, closing, or quitting until saving succeeds, preserving the unsaved buffer. The Close button and Command-W dismiss Conjure, including any open popup; leaving Notes or switching destinations removes its popup completely and restores the launcher’s original dimensions.
|
||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|||||||
@@ -619,6 +619,15 @@ private final class NotesView: NSView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class RoundedImageAttachmentCell: NSTextAttachmentCell {
|
||||||
|
override func draw(withFrame cellFrame: NSRect, in controlView: NSView?) {
|
||||||
|
NSGraphicsContext.saveGraphicsState()
|
||||||
|
defer { NSGraphicsContext.restoreGraphicsState() }
|
||||||
|
NSBezierPath(roundedRect: cellFrame, xRadius: 8, yRadius: 8).addClip()
|
||||||
|
super.draw(withFrame: cellFrame, in: controlView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
enum MarkdownHighlighting {
|
enum MarkdownHighlighting {
|
||||||
@@ -677,6 +686,9 @@ enum MarkdownHighlighting {
|
|||||||
let size = image.size
|
let size = image.size
|
||||||
let scale = min(1, 460 / max(size.width, 1), 240 / max(size.height, 1))
|
let scale = min(1, 460 / max(size.width, 1), 240 / max(size.height, 1))
|
||||||
if scale < 1 { image.size = NSSize(width: size.width * scale, height: size.height * scale) }
|
if scale < 1 { image.size = NSSize(width: size.width * scale, height: size.height * scale) }
|
||||||
|
if !(cell is RoundedImageAttachmentCell) {
|
||||||
|
attachment.attachmentCell = RoundedImageAttachmentCell(imageCell: image)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
storage.removeAttribute(.strikethroughStyle, range: range)
|
storage.removeAttribute(.strikethroughStyle, range: range)
|
||||||
storage.addAttribute(hiddenMarkup, value: false, range: range)
|
storage.addAttribute(hiddenMarkup, value: false, range: range)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
- this should keep window placement invariant of corner window sizes
|
- this should keep window placement invariant of corner window sizes
|
||||||
- [x] conjure notes:
|
- [x] conjure notes:
|
||||||
- [x] add action to delete a note (currently selected note)
|
- [x] add action to delete a note (currently selected note)
|
||||||
- [ ] make images have rounded corners
|
- [x] make images have rounded corners
|
||||||
|
- [ ] bug: text flashes when inserting characters, which is visually disturbing.
|
||||||
- [ ] the guidelines for centering the application when dragged are blurry on some external monitors.
|
- [ ] the guidelines for centering the application when dragged are blurry on some external monitors.
|
||||||
- track down the cause and fix it.
|
- track down the cause and fix it.
|
||||||
@@ -41,6 +41,68 @@ func noteFormattingHidesSyntaxButPreservesMarkdownAndLiteralCode() throws {
|
|||||||
#expect(reloaded.contents.string == source)
|
#expect(reloaded.contents.string == source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test @MainActor
|
||||||
|
func noteImagesRenderRoundedWithoutChangingSavedImageData() throws {
|
||||||
|
_ = NSApplication.shared
|
||||||
|
let image = NSImage(size: NSSize(width: 80, height: 48), flipped: false) { rect in
|
||||||
|
NSColor.red.setFill()
|
||||||
|
rect.fill()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let tiff = try #require(image.tiffRepresentation)
|
||||||
|
let bitmap = try #require(NSBitmapImageRep(data: tiff))
|
||||||
|
let png = try #require(bitmap.representation(using: .png, properties: [:]))
|
||||||
|
let pasteboard = NSPasteboard.withUniqueName()
|
||||||
|
defer { pasteboard.releaseGlobally() }
|
||||||
|
pasteboard.setData(png, forType: .png)
|
||||||
|
let editor = NotesTextView(frame: NSRect(x: 0, y: 0, width: 200, height: 120))
|
||||||
|
editor.isRichText = true
|
||||||
|
editor.importsGraphics = true
|
||||||
|
#expect(editor.readSelection(from: pasteboard))
|
||||||
|
|
||||||
|
func checkRoundedDrawing() throws {
|
||||||
|
MarkdownHighlighting.apply(to: editor)
|
||||||
|
let layout = try #require(editor.layoutManager)
|
||||||
|
layout.ensureLayout(for: try #require(editor.textContainer))
|
||||||
|
let output = try #require(NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: 200, pixelsHigh: 120,
|
||||||
|
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
|
||||||
|
colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0))
|
||||||
|
let context = try #require(NSGraphicsContext(bitmapImageRep: output))
|
||||||
|
NSGraphicsContext.saveGraphicsState()
|
||||||
|
defer { NSGraphicsContext.restoreGraphicsState() }
|
||||||
|
NSGraphicsContext.current = context
|
||||||
|
context.cgContext.clear(CGRect(x: 0, y: 0, width: 200, height: 120))
|
||||||
|
layout.drawGlyphs(forGlyphRange: layout.glyphRange(forCharacterRange: NSRange(location: 0, length: 1),
|
||||||
|
actualCharacterRange: nil), at: .zero)
|
||||||
|
var left = 200, right = -1, top = 120, bottom = -1
|
||||||
|
for y in 0..<120 {
|
||||||
|
for x in 0..<200 where (output.colorAt(x: x, y: y)?.alphaComponent ?? 0) > 0.5 {
|
||||||
|
left = min(left, x); right = max(right, x)
|
||||||
|
top = min(top, y); bottom = max(bottom, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try #require(right > left && bottom > top)
|
||||||
|
for x in [left, right] {
|
||||||
|
for y in [top, bottom] {
|
||||||
|
#expect(try #require(output.colorAt(x: x, y: y)).alphaComponent < 0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let center = try #require(output.colorAt(x: (left + right) / 2, y: (top + bottom) / 2)?.usingColorSpace(.deviceRGB))
|
||||||
|
#expect(center.redComponent > 0.9 && center.greenComponent < 0.1 && center.alphaComponent > 0.9)
|
||||||
|
}
|
||||||
|
try checkRoundedDrawing()
|
||||||
|
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||||
|
defer { try? FileManager.default.removeItem(at: directory) }
|
||||||
|
let store = try NotesStore(directory: directory)
|
||||||
|
let id = try store.create()
|
||||||
|
try store.update(id: id, contents: editor.markdownContents)
|
||||||
|
let reloaded = try #require(NotesStore(directory: directory).notes.first)
|
||||||
|
let attachment = try #require(reloaded.contents.attribute(.attachment, at: 0, effectiveRange: nil) as? NSTextAttachment)
|
||||||
|
#expect(attachment.fileWrapper?.regularFileContents == png)
|
||||||
|
editor.setMarkdownContents(reloaded.contents)
|
||||||
|
try checkRoundedDrawing()
|
||||||
|
}
|
||||||
|
|
||||||
@Test @MainActor
|
@Test @MainActor
|
||||||
func hiddenNoteMarkupKeepsTypingDeletionFormattingAndUndoAtVisibleBoundaries() throws {
|
func hiddenNoteMarkupKeepsTypingDeletionFormattingAndUndoAtVisibleBoundaries() throws {
|
||||||
_ = NSApplication.shared
|
_ = NSApplication.shared
|
||||||
|
|||||||
Reference in New Issue
Block a user