Skip to content

Commit

Permalink
Redesign tags and highlighting (padding, tag shadow, highlight outline)
Browse files Browse the repository at this point in the history
  • Loading branch information
chylex committed Mar 29, 2021
1 parent 324296b commit d2ae335
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 52 deletions.
9 changes: 6 additions & 3 deletions src/main/kotlin/org/acejump/session/Session.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import org.acejump.input.EditorKeyListener
import org.acejump.input.JumpMode
import org.acejump.input.JumpModeTracker
import org.acejump.input.KeyLayoutCache
import org.acejump.search.*
import org.acejump.search.Pattern
import org.acejump.search.SearchProcessor
import org.acejump.search.Tagger
import org.acejump.search.TaggingResult
import org.acejump.view.TagCanvas
import org.acejump.view.TextHighlighter

Expand Down Expand Up @@ -105,7 +108,7 @@ class Session(private val editor: Editor) {

is TaggingResult.Mark -> {
val tags = result.tags
tagCanvas.setMarkers(tags, isRegex = query is SearchQuery.RegularExpression)
tagCanvas.setMarkers(tags)

val cache = EditorOffsetCache.new()
val boundaries = StandardBoundaries.VISIBLE_ON_SCREEN
Expand All @@ -122,7 +125,7 @@ class Session(private val editor: Editor) {
*/
fun startRegexSearch(pattern: String, boundaries: Boundaries) {
tagger = Tagger(editor)
tagCanvas.setMarkers(emptyList(), isRegex = true)
tagCanvas.setMarkers(emptyList())

val processor = SearchProcessor.fromRegex(editor, pattern, boundaries.intersection(defaultBoundaries)).also { searchProcessor = it }
updateSearch(processor, markImmediately = true)
Expand Down
53 changes: 21 additions & 32 deletions src/main/kotlin/org/acejump/view/Tag.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class Tag(
private val length = tag.length

companion object {
const val ARC = 1
private const val ARC = 1

/**
* Creates a new tag, precomputing some information about the nearby characters to reduce rendering overhead. If the last typed
Expand All @@ -51,16 +51,25 @@ internal class Tag(
*/
private fun drawHighlight(g: Graphics2D, rect: Rectangle, color: Color) {
g.color = color
g.fillRoundRect(rect.x, rect.y + 1, rect.width, rect.height - 1, ARC, ARC)
g.fillRoundRect(rect.x, rect.y, rect.width, rect.height + 1, ARC, ARC)
}

/**
* Renders the tag text.
*/
private fun drawForeground(g: Graphics2D, font: TagFont, point: Point, text: String) {
val x = point.x + 2
val y = point.y + font.baselineDistance

g.font = font.tagFont

if (!ColorUtil.isDark(AceConfig.tagForegroundColor)) {
g.color = Color(0F, 0F, 0F, 0.35F)
g.drawString(text, x + 1, y + 1)
}

g.color = AceConfig.tagForegroundColor
g.drawString(text, point.x, point.y + font.baselineDistance)
g.drawString(text, x, y)
}
}

Expand All @@ -72,56 +81,36 @@ internal class Tag(
return offsetL in range
}

/**
* Determines on which side of the target character the tag is positioned.
*/
enum class TagAlignment {
LEFT,
RIGHT
}

/**
* Paints the tag, taking into consideration visual space around characters in the editor, as well as all other previously painted tags.
* Returns a rectangle indicating the area where the tag was rendered, or null if the tag could not be rendered due to overlap.
*/
fun paint(
g: Graphics2D, editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: MutableList<Rectangle>, isRegex: Boolean
): Rectangle? {
val (rect, alignment) = alignTag(editor, cache, font, occupied) ?: return null

val highlightColor = when {
alignment != TagAlignment.RIGHT || hasSpaceRight || isRegex -> AceConfig.tagBackgroundColor
else -> ColorUtil.darker(AceConfig.tagBackgroundColor, 3)
}
fun paint(g: Graphics2D, editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: MutableList<Rectangle>): Rectangle? {
val rect = alignTag(editor, cache, font, occupied) ?: return null

drawHighlight(g, rect, highlightColor)
drawHighlight(g, rect, AceConfig.tagBackgroundColor)
drawForeground(g, font, rect.location, tag)

occupied.add(JBUIScale.scale(2).let { Rectangle(rect.x - it, rect.y, rect.width + (2 * it), rect.height) })
return rect
}

private fun alignTag(editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: List<Rectangle>): Pair<Rectangle, TagAlignment>? {
private fun alignTag(editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: List<Rectangle>): Rectangle? {
val boundaries = StandardBoundaries.VISIBLE_ON_SCREEN

if (hasSpaceRight || offsetL == 0 || editor.immutableText[offsetL - 1].let { it == '\n' || it == '\r' }) {
val rectR = createRightAlignedTagRect(editor, cache, font)

return (rectR to TagAlignment.RIGHT).takeIf {
boundaries.isOffsetInside(editor, offsetR, cache) && occupied.none(rectR::intersects)
}
return rectR.takeIf { boundaries.isOffsetInside(editor, offsetR, cache) && occupied.none(rectR::intersects) }
}

val rectL = createLeftAlignedTagRect(editor, cache, font)

if (occupied.none(rectL::intersects)) {
return (rectL to TagAlignment.LEFT).takeIf { boundaries.isOffsetInside(editor, offsetL, cache) }
return rectL.takeIf { boundaries.isOffsetInside(editor, offsetL, cache) }
}

val rectR = createRightAlignedTagRect(editor, cache, font)

if (occupied.none(rectR::intersects)) {
return (rectR to TagAlignment.RIGHT).takeIf { boundaries.isOffsetInside(editor, offsetR, cache) }
return rectR.takeIf { boundaries.isOffsetInside(editor, offsetR, cache) }
}

return null
Expand All @@ -130,12 +119,12 @@ internal class Tag(
private fun createRightAlignedTagRect(editor: Editor, cache: EditorOffsetCache, font: TagFont): Rectangle {
val pos = cache.offsetToXY(editor, offsetR)
val shift = font.editorFontMetrics.charWidth(editor.immutableText[offsetR]) + (font.tagCharWidth * shiftR)
return Rectangle(pos.x + shift, pos.y, font.tagCharWidth * length, font.lineHeight)
return Rectangle(pos.x + shift, pos.y, (font.tagCharWidth * length) + 4, font.lineHeight)
}

private fun createLeftAlignedTagRect(editor: Editor, cache: EditorOffsetCache, font: TagFont): Rectangle {
val pos = cache.offsetToXY(editor, offsetL)
val shift = -(font.tagCharWidth * length)
return Rectangle(pos.x + shift, pos.y, font.tagCharWidth * length, font.lineHeight)
return Rectangle(pos.x + shift - 4, pos.y, (font.tagCharWidth * length) + 4, font.lineHeight)
}
}
16 changes: 3 additions & 13 deletions src/main/kotlin/org/acejump/view/TagCanvas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.CaretEvent
import com.intellij.openapi.editor.event.CaretListener
import com.intellij.ui.ColorUtil
import org.acejump.boundaries.EditorOffsetCache
import org.acejump.boundaries.StandardBoundaries
import org.acejump.config.AceConfig
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Rectangle
Expand All @@ -20,7 +18,6 @@ import javax.swing.SwingUtilities
*/
internal class TagCanvas(private val editor: Editor) : JComponent(), CaretListener {
private var markers: List<Tag>? = null
private var isRegex = false

init {
val contentComponent = editor.contentComponent
Expand Down Expand Up @@ -48,9 +45,8 @@ internal class TagCanvas(private val editor: Editor) : JComponent(), CaretListen
repaint()
}

fun setMarkers(markers: List<Tag>, isRegex: Boolean) {
fun setMarkers(markers: List<Tag>) {
this.markers = markers
this.isRegex = isRegex
repaint()
}

Expand Down Expand Up @@ -84,18 +80,12 @@ internal class TagCanvas(private val editor: Editor) : JComponent(), CaretListen

val caretOffset = editor.caretModel.offset
val caretMarker = markers.find { it.offsetL == caretOffset || it.offsetR == caretOffset }
val caretRect = caretMarker?.paint(g, editor, cache, font, occupied, isRegex)
caretMarker?.paint(g, editor, cache, font, occupied)

for (marker in markers) {
if (marker.isOffsetInRange(viewRange) && marker !== caretMarker) {
marker.paint(g, editor, cache, font, occupied, isRegex)
marker.paint(g, editor, cache, font, occupied)
}
}

if (caretRect != null) {
g.color = ColorUtil.brighter(AceConfig.tagBackgroundColor, 10)
// Only adding 1 to width because it seems the right side of the tag highlight is slightly off.
g.drawRoundRect(caretRect.x - 1, caretRect.y, caretRect.width + 1, caretRect.height, Tag.ARC, Tag.ARC)
}
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/org/acejump/view/TagFont.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.awt.FontMetrics
*/
internal class TagFont(editor: Editor) {
val tagFont: Font = editor.colorsScheme.getFont(EditorFontType.BOLD)
val tagCharWidth = editor.component.getFontMetrics(tagFont).charWidth('w')
val tagCharWidth = editor.component.getFontMetrics(tagFont).charWidth('W')

val editorFontMetrics: FontMetrics = editor.component.getFontMetrics(editor.colorsScheme.getFont(EditorFontType.PLAIN))
val lineHeight = editor.lineHeight
Expand Down
12 changes: 9 additions & 3 deletions src/main/kotlin/org/acejump/view/TextHighlighter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ internal class TextHighlighter(private val editor: Editor) {
val end = EditorOffsetCache.Uncached.offsetToXY(editor, endOffset)

g.color = AceConfig.textHighlightColor
g.fillRoundRect(start.x, start.y + 1, end.x - start.x, editor.lineHeight - 1, Tag.ARC, Tag.ARC)
g.fillRect(start.x, start.y + 1, end.x - start.x, editor.lineHeight - 1)

g.color = AceConfig.tagBackgroundColor
g.drawRect(start.x, start.y, end.x - start.x, editor.lineHeight)
}
}

Expand All @@ -111,7 +114,7 @@ internal class TextHighlighter(private val editor: Editor) {
val end = EditorOffsetCache.Uncached.offsetToXY(editor, endOffset)

g.color = AceConfig.targetModeColor
g.drawRoundRect(max(0, start.x - JBUI.scale(1)), start.y, end.x - start.x + JBUI.scale(2), editor.lineHeight, Tag.ARC, Tag.ARC)
g.drawRect(max(0, start.x - JBUI.scale(1)), start.y, end.x - start.x + JBUI.scale(2), editor.lineHeight)
}
}

Expand All @@ -130,7 +133,10 @@ internal class TextHighlighter(private val editor: Editor) {
val lastCharWidth = editor.component.getFontMetrics(font).charWidth(char)

g.color = AceConfig.textHighlightColor
g.fillRoundRect(pos.x, pos.y + 1, lastCharWidth, editor.lineHeight - 1, Tag.ARC, Tag.ARC)
g.fillRect(pos.x, pos.y + 1, lastCharWidth, editor.lineHeight - 1)

g.color = AceConfig.tagBackgroundColor
g.drawRect(pos.x, pos.y, lastCharWidth, editor.lineHeight)
}
}
}

0 comments on commit d2ae335

Please sign in to comment.