IJPL-158348 install ui - use separate state to avoid NPEs

GitOrigin-RevId: 0a15ee52a17a5ee6da8f096eaf9a2e0a260af030
This commit is contained in:
Vladimir Krivosheev
2024-07-26 16:22:43 +02:00
committed by intellij-monorepo-bot
parent 2751a94633
commit e89669ffbf
14 changed files with 489 additions and 381 deletions

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.ui;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.ui.Animator;
import java.awt.*;
@@ -10,31 +9,31 @@ import java.awt.*;
* @author Konstantin Bulenkov
*/
public final class DialogEarthquakeShaker {
private final Window myWindow;
private Point myNaturalLocation;
private long myStartTime;
private final Window window;
private Point naturalLocation;
private long startTime;
private DialogEarthquakeShaker(Window window) {
myWindow = window;
this.window = window;
}
public void startShake() {
myNaturalLocation = myWindow.getLocation();
myStartTime = System.currentTimeMillis();
naturalLocation = window.getLocation();
startTime = System.currentTimeMillis();
new Animator("EarthQuake", 10, 70, true) {
@Override
public void paintNow(int frame, int totalFrames, int cycle) {
final long elapsed = System.currentTimeMillis() - myStartTime;
final long elapsed = System.currentTimeMillis() - startTime;
final double waveOffset = (elapsed % 70) / 70d;
final double angle = waveOffset * 2d * Math.PI;
final int shakenX = (int)((Math.sin(angle) * 10) + myNaturalLocation.x);
myWindow.setLocation(shakenX, myNaturalLocation.y);
myWindow.repaint();
final int shakenX = (int)((Math.sin(angle) * 10) + naturalLocation.x);
window.setLocation(shakenX, naturalLocation.y);
window.repaint();
if (elapsed > 150) {
suspend();
myWindow.setLocation(myNaturalLocation);
myWindow.repaint();
Disposer.dispose(this);
window.setLocation(naturalLocation);
window.repaint();
dispose();
}
}
}.resume();

View File

@@ -74,9 +74,9 @@ open class LoadingDecorator @JvmOverloads constructor(
pane.repaint()
},
)
Disposer.register(parent, fadeOutAnimator)
pane.add(content, JLayeredPane.DEFAULT_LAYER, 0)
Disposer.register(parent) {
fadeOutAnimator.dispose()
loadingLayer.progress.dispose()
startRequestJob?.cancel()
}

View File

@@ -1,12 +1,19 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ui.components
import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.util.Key
import com.intellij.ui.ClientProperty
import com.intellij.ui.scale.JBUIScale
import com.intellij.util.MathUtil
import com.intellij.util.ui.*
import com.intellij.util.ui.UIUtil.ComponentStyle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import org.jetbrains.annotations.ApiStatus
import java.awt.*
import java.awt.event.*
@@ -28,22 +35,13 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
private val listener = Listener()
private val scrollTimer = TimerUtil.createNamedTimer("ScrollBarThumbScrollTimer", 60, listener)
@JvmField
protected var scrollBar: JScrollBar? = null
@JvmField
protected val myTrack: ScrollBarPainter.Track = ScrollBarPainter.Track({ scrollBar })
@Suppress("LeakingThis")
@JvmField
protected val thumb: ScrollBarPainter.Thumb = createThumbPainter()
internal var installedState: DefaultScrollbarUiInstalledState? = null
private set
private var isValueCached: Boolean = false
private var cachedValue: Int = 0
private var oldValue: Int = 0
@JvmField
protected var animationBehavior: ScrollBarAnimationBehavior? = null
companion object {
@ApiStatus.Internal
@JvmField
@@ -64,53 +62,60 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
}
}
protected open fun createWrapAnimationBehaviour(): ScrollBarAnimationBehavior {
internal open fun createWrapAnimationBehaviour(state: DefaultScrollbarUiInstalledState): ScrollBarAnimationBehavior {
return ToggleableScrollBarAnimationBehaviorDecorator(
decoratedBehavior = createBaseAnimationBehavior(),
trackAnimator = myTrack.animator,
thumbAnimator = thumb.animator,
decoratedBehavior = createBaseAnimationBehavior(state),
trackAnimator = state.track.animator,
thumbAnimator = state.thumb.animator,
)
}
protected open fun createThumbPainter(): ScrollBarPainter.Thumb {
return ScrollBarPainter.Thumb({ scrollBar }, false)
internal open fun createThumbPainter(state: DefaultScrollbarUiInstalledState): ScrollBarPainter.Thumb {
return ScrollBarPainter.Thumb({ state.scrollBar }, false, state.coroutineScope)
}
protected open fun createBaseAnimationBehavior(): ScrollBarAnimationBehavior {
return DefaultScrollBarAnimationBehavior(trackAnimator = myTrack.animator, thumbAnimator = thumb.animator)
internal open fun createBaseAnimationBehavior(state: DefaultScrollbarUiInstalledState): ScrollBarAnimationBehavior {
return DefaultScrollBarAnimationBehavior(trackAnimator = state.track.animator, thumbAnimator = state.thumb.animator)
}
private fun getEffectiveThickness(): Int = scale(if (scrollBar == null || isOpaque(scrollBar!!)) thickness else thicknessMax)
private fun getEffectiveThickness(): Int {
val scrollBar = installedState?.scrollBar
return scale(if (scrollBar == null || isOpaque(scrollBar)) thickness else thicknessMax)
}
private fun getMinimalThickness(): Int = scale(if (scrollBar == null || isOpaque(scrollBar!!)) thickness else thicknessMin)
private fun getMinimalThickness(): Int {
val scrollBar = installedState?.scrollBar
return scale(if (scrollBar == null || isOpaque(scrollBar)) thickness else thicknessMin)
}
fun toggle(isOn: Boolean) {
if (animationBehavior != null) {
animationBehavior!!.onToggle(isOn)
}
installedState?.animationBehavior?.onToggle(isOn)
}
open fun isAbsolutePositioning(event: MouseEvent): Boolean = SwingUtilities.isMiddleMouseButton(event)
open fun isTrackClickable(): Boolean = isOpaque(scrollBar!!) || (animationBehavior != null && animationBehavior!!.trackFrame > 0)
open fun isTrackClickable(): Boolean {
val state = installedState ?: return false
return isOpaque(state.scrollBar) || state.animationBehavior.trackFrame > 0
}
open val isTrackExpandable: Boolean
get() = false
fun isTrackContains(x: Int, y: Int): Boolean {
return myTrack.bounds.contains(x, y)
return installedState!!.track.bounds.contains(x, y)
}
fun isThumbContains(x: Int, y: Int): Boolean {
return thumb.bounds.contains(x, y)
return installedState!!.thumb.bounds.contains(x, y)
}
protected open fun paintTrack(g: Graphics2D, c: JComponent) {
paint(myTrack, g, c, false)
paint(p = installedState!!.track, g = g, c = c, small = false)
}
protected open fun paintThumb(g: Graphics2D, c: JComponent) {
paint(thumb, g, c, ScrollSettings.isThumbSmallIfOpaque.invoke() && isOpaque(c))
internal open fun paintThumb(g: Graphics2D, c: JComponent, state: DefaultScrollbarUiInstalledState) {
paint(p = installedState!!.thumb, g = g, c = c, small = ScrollSettings.isThumbSmallIfOpaque.invoke() && isOpaque(c))
}
fun paint(p: ScrollBarPainter, g: Graphics2D, c: JComponent?, small: Boolean) {
@@ -149,24 +154,29 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
}
private fun getTrackOffset(offset: Int): Int {
if (!isTrackExpandable) return offset
val value: Float = if (animationBehavior == null) 0f else animationBehavior!!.trackFrame
if (value <= 0) return offset
if (value >= 1) return 0
return (.5f + offset * (1 - value)).toInt()
if (!isTrackExpandable) {
return offset
}
val value = installedState?.animationBehavior?.trackFrame ?: 0f
return when {
value <= 0 -> offset
value >= 1 -> 0
else -> (.5f + offset * (1 - value)).toInt()
}
}
fun repaint() {
if (scrollBar != null) scrollBar!!.repaint()
installedState?.scrollBar?.repaint()
}
fun repaint(x: Int, y: Int, width: Int, height: Int) {
if (scrollBar != null) scrollBar!!.repaint(x, y, width, height)
installedState?.scrollBar?.repaint(x, y, width, height)
}
private fun scale(value: Int): Int {
val scaledValue = JBUIScale.scale(value)
return when (UIUtil.getComponentStyle(scrollBar)) {
return when (UIUtil.getComponentStyle(installedState!!.scrollBar)) {
ComponentStyle.LARGE -> (scaledValue * 1.15).toInt()
ComponentStyle.SMALL -> (scaledValue * 0.857).toInt()
ComponentStyle.MINI -> (scaledValue * 0.714).toInt()
@@ -175,39 +185,42 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
}
override fun installUI(c: JComponent) {
animationBehavior = createWrapAnimationBehaviour()
val scrollBar = c as JScrollBar
installedState = DefaultScrollbarUiInstalledState(ui = this, scrollBar = scrollBar)
scrollBar = c as JScrollBar
ScrollBarPainter.setBackground(c)
scrollBar!!.isOpaque = false
scrollBar!!.isFocusable = false
scrollBar!!.addMouseListener(listener)
scrollBar!!.addMouseMotionListener(listener)
scrollBar!!.model.addChangeListener(listener)
scrollBar!!.addPropertyChangeListener(listener)
scrollBar!!.addFocusListener(listener)
scrollBar.isOpaque = false
scrollBar.isFocusable = false
scrollBar.addMouseListener(listener)
scrollBar.addMouseMotionListener(listener)
scrollBar.model.addChangeListener(listener)
scrollBar.addPropertyChangeListener(listener)
scrollBar.addFocusListener(listener)
scrollTimer.initialDelay = 300
}
override fun uninstallUI(c: JComponent) {
checkNotNull(animationBehavior)
animationBehavior!!.onUninstall()
animationBehavior = null
val state = installedState ?: return
state.coroutineScope.cancel()
installedState = null
state.animationBehavior.onUninstall()
scrollTimer.stop()
scrollBar!!.removeFocusListener(listener)
scrollBar!!.removePropertyChangeListener(listener)
scrollBar!!.model.removeChangeListener(listener)
scrollBar!!.removeMouseMotionListener(listener)
scrollBar!!.removeMouseListener(listener)
scrollBar!!.foreground = null
scrollBar!!.background = null
scrollBar = null
val scrollBar = state.scrollBar
scrollBar.removeFocusListener(listener)
scrollBar.removePropertyChangeListener(listener)
scrollBar.model.removeChangeListener(listener)
scrollBar.removeMouseMotionListener(listener)
scrollBar.removeMouseListener(listener)
scrollBar.foreground = null
scrollBar.background = null
}
override fun getPreferredSize(c: JComponent): Dimension {
val thickness: Int = getEffectiveThickness()
val alignment: JBScrollPane.Alignment = JBScrollPane.Alignment.get(c)
val scrollBar = installedState!!.scrollBar
val thickness = getEffectiveThickness()
val alignment = JBScrollPane.Alignment.get(c)
val preferred = Dimension(thickness, thickness)
if (alignment == JBScrollPane.Alignment.LEFT || alignment == JBScrollPane.Alignment.RIGHT) {
preferred.height += preferred.height
@@ -223,100 +236,115 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
}
override fun paint(g: Graphics, c: JComponent) {
val alignment: JBScrollPane.Alignment? = JBScrollPane.Alignment.get(c)
if (alignment != null && g is Graphics2D) {
val background: Color? = if (!isOpaque(c)) null else c.background
if (background != null) {
g.setColor(background)
g.fillRect(0, 0, c.width, c.height)
}
val bounds = Rectangle(c.width, c.height)
JBInsets.removeFrom(bounds, c.insets)
// process an area before the track
val leading: Component? = ClientProperty.get(c, LEADING)
if (leading != null) {
if (alignment == JBScrollPane.Alignment.LEFT || alignment == JBScrollPane.Alignment.RIGHT) {
val size: Int = leading.preferredSize.height
leading.setBounds(bounds.x, bounds.y, bounds.width, size)
bounds.height -= size
bounds.y += size
}
else {
val size: Int = leading.preferredSize.width
leading.setBounds(bounds.x, bounds.y, size, bounds.height)
bounds.width -= size
bounds.x += size
}
}
// process an area after the track
val trailing: Component? = ClientProperty.get(c, TRAILING)
if (trailing != null) {
if (alignment == JBScrollPane.Alignment.LEFT || alignment == JBScrollPane.Alignment.RIGHT) {
val size: Int = trailing.preferredSize.height
bounds.height -= size
trailing.setBounds(bounds.x, bounds.y + bounds.height, bounds.width, size)
}
else {
val size: Int = trailing.preferredSize.width
bounds.width -= size
trailing.setBounds(bounds.x + bounds.width, bounds.y, size, bounds.height)
}
}
// do not set track size bigger that expected thickness
val alignment = JBScrollPane.Alignment.get(c)
if (alignment == null || g !is Graphics2D) {
return
}
val background: Color? = if (isOpaque(c)) c.background else null
if (background != null) {
g.setColor(background)
g.fillRect(0, 0, c.width, c.height)
}
val bounds = Rectangle(c.width, c.height)
JBInsets.removeFrom(bounds, c.insets)
// process an area before the track
val leading: Component? = ClientProperty.get(c, LEADING)
if (leading != null) {
if (alignment == JBScrollPane.Alignment.LEFT || alignment == JBScrollPane.Alignment.RIGHT) {
val offset: Int = bounds.width - getEffectiveThickness()
if (offset > 0) {
bounds.width -= offset
if (alignment == JBScrollPane.Alignment.RIGHT) bounds.x += offset
}
val size = leading.preferredSize.height
leading.setBounds(bounds.x, bounds.y, bounds.width, size)
bounds.height -= size
bounds.y += size
}
else {
val offset: Int = bounds.height - getEffectiveThickness()
if (offset > 0) {
bounds.height -= offset
if (alignment == JBScrollPane.Alignment.BOTTOM) bounds.y += offset
}
val size: Int = leading.preferredSize.width
leading.setBounds(bounds.x, bounds.y, size, bounds.height)
bounds.width -= size
bounds.x += size
}
val animate: Boolean = myTrack.bounds != bounds // animate thumb on resize
if (animate) myTrack.bounds.bounds = bounds
updateThumbBounds(animate)
paintTrack(g, c)
// process additional drawing on the track
val track: RegionPainter<Any>? = ClientProperty.get(c, JBScrollBar.TRACK)
if (track != null && myTrack.bounds.width > 0 && myTrack.bounds.height > 0) {
track.paint(g, myTrack.bounds.x, myTrack.bounds.y, myTrack.bounds.width, myTrack.bounds.height, null)
}
// process an area after the track
val trailing: Component? = ClientProperty.get(c, TRAILING)
if (trailing != null) {
if (alignment == JBScrollPane.Alignment.LEFT || alignment == JBScrollPane.Alignment.RIGHT) {
val size: Int = trailing.preferredSize.height
bounds.height -= size
trailing.setBounds(bounds.x, bounds.y + bounds.height, bounds.width, size)
}
// process drawing the thumb
if (thumb.bounds.width > 0 && thumb.bounds.height > 0) {
paintThumb(g, c)
else {
val size: Int = trailing.preferredSize.width
bounds.width -= size
trailing.setBounds(bounds.x + bounds.width, bounds.y, size, bounds.height)
}
}
// do not set track size bigger that expected thickness
if (alignment == JBScrollPane.Alignment.LEFT || alignment == JBScrollPane.Alignment.RIGHT) {
val offset: Int = bounds.width - getEffectiveThickness()
if (offset > 0) {
bounds.width -= offset
if (alignment == JBScrollPane.Alignment.RIGHT) bounds.x += offset
}
}
else {
val offset: Int = bounds.height - getEffectiveThickness()
if (offset > 0) {
bounds.height -= offset
if (alignment == JBScrollPane.Alignment.BOTTOM) bounds.y += offset
}
}
val state = installedState!!
val track = state.track
// animate thumb on resize
val animate = track.bounds != bounds
if (animate) {
track.bounds.bounds = bounds
}
updateThumbBounds(animate)
paintTrack(g, c)
// process additional drawing on the track
val trackPainter: RegionPainter<Any>? = ClientProperty.get(c, JBScrollBar.TRACK)
if (trackPainter != null && track.bounds.width > 0 && track.bounds.height > 0) {
trackPainter.paint(g, track.bounds.x, track.bounds.y, track.bounds.width, track.bounds.height, null)
}
// process drawing the thumb
if (state.thumb.bounds.width > 0 && state.thumb.bounds.height > 0) {
paintThumb(g, c, state)
}
}
private fun updateThumbBounds(animate: Boolean) {
var animate = animate
var value = 0
val min: Int = scrollBar!!.minimum
val max: Int = scrollBar!!.maximum
val state = installedState!!
val scrollBar = state.scrollBar
val min: Int = scrollBar.minimum
val max: Int = scrollBar.maximum
val range: Int = max - min
if (range <= 0) {
thumb.bounds.setBounds(0, 0, 0, 0)
state.thumb.bounds.setBounds(0, 0, 0, 0)
}
else if (Adjustable.VERTICAL == scrollBar!!.orientation) {
val extent = scrollBar!!.visibleAmount
else if (Adjustable.VERTICAL == scrollBar.orientation) {
val extent = scrollBar.visibleAmount
val track = state.track
val height = max(
convert(newRange = myTrack.bounds.height.toDouble(), oldValue = extent.toDouble(), oldRange = range.toDouble()).toDouble(),
convert(newRange = track.bounds.height.toDouble(), oldValue = extent.toDouble(), oldRange = range.toDouble()).toDouble(),
(2 * getEffectiveThickness()).toDouble(),
).toInt()
if (myTrack.bounds.height <= height) {
thumb.bounds.setBounds(0, 0, 0, 0)
if (track.bounds.height <= height) {
state.thumb.bounds.setBounds(0, 0, 0, 0)
}
else {
value = this.value
val maxY = myTrack.bounds.y + myTrack.bounds.height - height
val maxY = track.bounds.y + track.bounds.height - height
val y = if ((value < max - extent)) {
convert(
newRange = (myTrack.bounds.height - height).toDouble(),
newRange = (track.bounds.height - height).toDouble(),
oldValue = (value - min).toDouble(),
oldRange = (range - extent).toDouble(),
)
@@ -324,45 +352,46 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
else {
maxY
}
thumb.bounds.setBounds(myTrack.bounds.x, adjust(y, myTrack.bounds.y, maxY), myTrack.bounds.width, height)
state.thumb.bounds.setBounds(track.bounds.x, adjust(y, track.bounds.y, maxY), track.bounds.width, height)
// animate thumb on move
animate = animate or (oldValue != value)
}
}
else {
val extent: Int = scrollBar!!.visibleAmount
val track = state.track
val extent = scrollBar.visibleAmount
val width: Int = max(
convert(
newRange = myTrack.bounds.width.toDouble(),
newRange = track.bounds.width.toDouble(),
oldValue = extent.toDouble(),
oldRange = range.toDouble(),
).toDouble(),
(2 * getEffectiveThickness()).toDouble(),
).toInt()
if (myTrack.bounds.width <= width) {
thumb.bounds.setBounds(0, 0, 0, 0)
if (track.bounds.width <= width) {
state.thumb.bounds.setBounds(0, 0, 0, 0)
}
else {
value = this.value
val maxX: Int = myTrack.bounds.x + myTrack.bounds.width - width
var x: Int = if ((value < max - extent)) convert((myTrack.bounds.width - width).toDouble(), (value - min).toDouble(),
val maxX: Int = track.bounds.x + track.bounds.width - width
var x: Int = if ((value < max - extent)) convert((track.bounds.width - width).toDouble(), (value - min).toDouble(),
(range - extent).toDouble())
else maxX
if (!scrollBar!!.componentOrientation.isLeftToRight) {
x = myTrack.bounds.x - x + maxX
if (!scrollBar.componentOrientation.isLeftToRight) {
x = track.bounds.x - x + maxX
}
thumb.bounds.setBounds(adjust(x, myTrack.bounds.x, maxX), myTrack.bounds.y, width, myTrack.bounds.height)
state.thumb.bounds.setBounds(adjust(x, track.bounds.x, maxX), track.bounds.y, width, track.bounds.height)
animate = animate or (oldValue != value) // animate thumb on move
}
}
oldValue = value
if (animate && animationBehavior != null) {
animationBehavior!!.onThumbMove()
if (animate) {
state.animationBehavior.onThumbMove()
}
}
private val value: Int
get() = if (isValueCached) cachedValue else scrollBar!!.value
get() = if (isValueCached) cachedValue else installedState!!.scrollBar.value
private inner class Listener : MouseAdapter(), ActionListener, FocusListener, ChangeListener, PropertyChangeListener {
private var myOffset: Int = 0
@@ -375,12 +404,13 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
fun updateMouse(x: Int, y: Int) {
if (isTrackContains(x, y)) {
if (!isOverTrack && animationBehavior != null) {
animationBehavior!!.onTrackHover(true.also { isOverTrack = it })
val animationBehavior = installedState?.animationBehavior ?: return
if (!isOverTrack) {
animationBehavior.onTrackHover(true.also { isOverTrack = true })
}
val hover: Boolean = isThumbContains(x, y)
if (isOverThumb != hover && animationBehavior != null) {
animationBehavior!!.onThumbHover(hover.also { isOverThumb = it })
if (isOverThumb != hover) {
animationBehavior.onThumbHover(hover.also { isOverThumb = it })
}
}
else {
@@ -389,11 +419,12 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
}
fun updateMouseExit() {
if (isOverThumb && animationBehavior != null) {
animationBehavior!!.onThumbHover(false.also { isOverThumb = it })
val animationBehavior = installedState?.animationBehavior ?: return
if (isOverThumb) {
animationBehavior.onThumbHover(false.also { isOverThumb = false })
}
if (isOverTrack && animationBehavior != null) {
animationBehavior!!.onTrackHover(false.also { isOverTrack = it })
if (isOverTrack) {
animationBehavior.onTrackHover(false.also { isOverTrack = false })
}
}
@@ -403,36 +434,39 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
}
// redispatch current event to the view
val parent: Container = scrollBar!!.parent
if (parent is JScrollPane) {
val view: Component? = parent.viewport.view
if (view != null) {
val point: Point = event.locationOnScreen
SwingUtilities.convertPointFromScreen(point, view)
val target: Component = SwingUtilities.getDeepestComponentAt(view, point.x, point.y)
MouseEventAdapter.redispatch(event, target)
}
val parent = installedState?.scrollBar?.parent
val view = (parent as? JScrollPane)?.viewport?.view
if (view != null) {
val point = event.locationOnScreen
SwingUtilities.convertPointFromScreen(point, view)
MouseEventAdapter.redispatch(event, SwingUtilities.getDeepestComponentAt(view, point.x, point.y))
}
return true
}
override fun mouseClicked(e: MouseEvent) {
if (scrollBar != null && scrollBar!!.isEnabled) redispatchIfTrackNotClickable(e)
val scrollBar = installedState?.scrollBar ?: return
if (scrollBar.isEnabled) {
redispatchIfTrackNotClickable(e)
}
}
override fun mousePressed(event: MouseEvent) {
if (scrollBar == null || !scrollBar!!.isEnabled) return
if (redispatchIfTrackNotClickable(event)) return
if (SwingUtilities.isRightMouseButton(event)) return
val scrollBar = installedState?.scrollBar ?: return
if (!scrollBar.isEnabled || redispatchIfTrackNotClickable(event) || SwingUtilities.isRightMouseButton(event)) {
return
}
isValueCached = true
cachedValue = scrollBar!!.value
scrollBar!!.valueIsAdjusting = true
cachedValue = scrollBar.value
scrollBar.valueIsAdjusting = true
myMouseX = event.x
myMouseY = event.y
val vertical: Boolean = Adjustable.VERTICAL == scrollBar!!.orientation
val thumb = installedState!!.thumb
val vertical = Adjustable.VERTICAL == scrollBar.orientation
if (isThumbContains(myMouseX, myMouseY)) {
// pressed on the thumb
myOffset = if (vertical) (myMouseY - thumb.bounds.y) else (myMouseX - thumb.bounds.x)
@@ -448,14 +482,14 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
else {
scrollTimer.stop()
isDragging = false
if (Adjustable.VERTICAL == scrollBar!!.orientation) {
val y: Int = if (thumb.bounds.isEmpty) scrollBar!!.height / 2 else thumb.bounds.y
if (Adjustable.VERTICAL == scrollBar.orientation) {
val y = if (thumb.bounds.isEmpty) scrollBar.height / 2 else thumb.bounds.y
isReversed = myMouseY < y
}
else {
val x: Int = if (thumb.bounds.isEmpty) scrollBar!!.width / 2 else thumb.bounds.x
val x = if (thumb.bounds.isEmpty) scrollBar.width / 2 else thumb.bounds.x
isReversed = myMouseX < x
if (!scrollBar!!.componentOrientation.isLeftToRight) {
if (!scrollBar.componentOrientation.isLeftToRight) {
isReversed = !isReversed
}
}
@@ -466,22 +500,41 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
}
override fun mouseReleased(event: MouseEvent) {
if (isDragging) updateMouse(event.x, event.y)
if (scrollBar == null || !scrollBar!!.isEnabled) return
scrollBar!!.valueIsAdjusting = false
if (redispatchIfTrackNotClickable(event)) return
if (SwingUtilities.isRightMouseButton(event)) return
if (isDragging) {
updateMouse(event.x, event.y)
}
val scrollBar = installedState?.scrollBar ?: return
if (!scrollBar.isEnabled) {
return
}
scrollBar.valueIsAdjusting = false
if (redispatchIfTrackNotClickable(event)) {
return
}
if (SwingUtilities.isRightMouseButton(event)) {
return
}
isDragging = false
myOffset = 0
scrollTimer.stop()
isValueCached = true
cachedValue = scrollBar!!.value
cachedValue = scrollBar.value
repaint()
}
override fun mouseDragged(event: MouseEvent) {
if (scrollBar == null || !scrollBar!!.isEnabled) return
if (thumb.bounds.isEmpty || SwingUtilities.isRightMouseButton(event)) return
val scrollBar = installedState?.scrollBar ?: return
if (!scrollBar.isEnabled) {
return
}
if (installedState!!.thumb.bounds.isEmpty || SwingUtilities.isRightMouseButton(event)) {
return
}
if (isDragging) {
setValueFrom(event)
}
@@ -494,28 +547,39 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
}
override fun mouseMoved(event: MouseEvent) {
if (scrollBar == null || !scrollBar!!.isEnabled) return
if (!isDragging) updateMouse(event.x, event.y)
val scrollBar = installedState?.scrollBar ?: return
if (!scrollBar.isEnabled) {
return
}
if (!isDragging) {
updateMouse(event.x, event.y)
}
}
override fun mouseExited(event: MouseEvent) {
if (scrollBar == null || !scrollBar!!.isEnabled) return
if (!isDragging) updateMouseExit()
val scrollBar = installedState?.scrollBar ?: return
if (!scrollBar.isEnabled) {
return
}
if (!isDragging) {
updateMouseExit()
}
}
override fun actionPerformed(event: ActionEvent) {
val scrollBar = installedState?.scrollBar
if (scrollBar == null) {
scrollTimer.stop()
}
else {
scroll(isReversed)
if (!thumb.bounds.isEmpty) {
if (!installedState!!.thumb.bounds.isEmpty) {
if (if (isReversed) !isMouseBeforeThumb() else !isMouseAfterThumb()) {
scrollTimer.stop()
}
}
val value: Int = scrollBar!!.value
if (if (isReversed) value <= scrollBar!!.minimum else value >= scrollBar!!.maximum - scrollBar!!.visibleAmount) {
val value: Int = scrollBar.value
if (if (isReversed) value <= scrollBar.minimum else value >= scrollBar.maximum - scrollBar.visibleAmount) {
scrollTimer.stop()
}
}
@@ -548,11 +612,12 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
repaint()
}
if ("opaque" == name || "visible" == name) {
if (animationBehavior != null) {
animationBehavior!!.onReset()
val state = installedState
if (state != null) {
state.animationBehavior.onReset()
state.track.bounds.setBounds(0, 0, 0, 0)
state.thumb.bounds.setBounds(0, 0, 0, 0)
}
myTrack.bounds.setBounds(0, 0, 0, 0)
thumb.bounds.setBounds(0, 0, 0, 0)
}
}
@@ -563,41 +628,41 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
val thumbMin: Int
val thumbMax: Int
val thumbPos: Int
if (Adjustable.VERTICAL == scrollBar!!.orientation) {
thumbMin = myTrack.bounds.y
thumbMax = myTrack.bounds.y + myTrack.bounds.height - thumb.bounds.height
val state = installedState ?: return
val track = state.track
val thumb = state.thumb
val scrollBar = state.scrollBar
if (Adjustable.VERTICAL == scrollBar.orientation) {
thumbMin = track.bounds.y
thumbMax = track.bounds.y + track.bounds.height - thumb.bounds.height
thumbPos = MathUtil.clamp(y - myOffset, thumbMin, thumbMax)
if (thumb.bounds.y != thumbPos) {
val minY: Int = min(thumb.bounds.y.toDouble(), thumbPos.toDouble()).toInt()
val maxY: Int = (max(thumb.bounds.y.toDouble(), thumbPos.toDouble()) + thumb.bounds.height).toInt()
thumb.bounds.y = thumbPos
if (animationBehavior != null) {
animationBehavior!!.onThumbMove()
}
state.animationBehavior.onThumbMove()
repaint(thumb.bounds.x, minY, thumb.bounds.width, maxY - minY)
}
}
else {
thumbMin = myTrack.bounds.x
thumbMax = myTrack.bounds.x + myTrack.bounds.width - thumb.bounds.width
thumbMin = track.bounds.x
thumbMax = track.bounds.x + track.bounds.width - thumb.bounds.width
thumbPos = MathUtil.clamp(x - myOffset, thumbMin, thumbMax)
if (thumb.bounds.x != thumbPos) {
val minX: Int = min(thumb.bounds.x.toDouble(), thumbPos.toDouble()).toInt()
val maxX: Int = (max(thumb.bounds.x.toDouble(), thumbPos.toDouble()) + thumb.bounds.width).toInt()
thumb.bounds.x = thumbPos
if (animationBehavior != null) {
animationBehavior!!.onThumbMove()
}
state.animationBehavior.onThumbMove()
repaint(minX, thumb.bounds.y, maxX - minX, thumb.bounds.height)
}
}
val valueMin: Int = scrollBar!!.minimum
val valueMax: Int = scrollBar!!.maximum - scrollBar!!.visibleAmount
val valueMin = scrollBar.minimum
val valueMax = scrollBar.maximum - scrollBar.visibleAmount
// If the thumb has reached the end of the scrollbar, then set the value to its maximum.
// Otherwise, compute the value as accurately as possible.
val isDefaultOrientation: Boolean = Adjustable.VERTICAL == scrollBar!!.orientation || scrollBar!!.componentOrientation.isLeftToRight
val isDefaultOrientation: Boolean = Adjustable.VERTICAL == scrollBar.orientation || scrollBar.componentOrientation.isLeftToRight
if (thumbPos == thumbMax) {
scrollBar!!.value = if (isDefaultOrientation) valueMax else valueMin
scrollBar.value = if (isDefaultOrientation) valueMax else valueMin
}
else {
val valueRange = valueMax - valueMin
@@ -605,9 +670,11 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
val thumbValue = if (isDefaultOrientation) thumbPos - thumbMin else thumbMax - thumbPos
isValueCached = true
cachedValue = valueMin + convert(valueRange.toDouble(), thumbValue.toDouble(), thumbRange.toDouble())
scrollBar!!.value = cachedValue
scrollBar.value = cachedValue
}
if (!isDragging) {
updateMouse(x, y)
}
if (!isDragging) updateMouse(x, y)
}
fun startScrollTimerIfNecessary() {
@@ -620,56 +687,75 @@ open class DefaultScrollBarUI @JvmOverloads internal constructor(
fun isMouseBeforeThumb(): Boolean {
return when {
Adjustable.VERTICAL == scrollBar!!.orientation -> isMouseOnTop()
scrollBar!!.componentOrientation.isLeftToRight -> isMouseOnLeft()
Adjustable.VERTICAL == installedState!!.scrollBar.orientation -> isMouseOnTop()
installedState!!.scrollBar.componentOrientation.isLeftToRight -> isMouseOnLeft()
else -> isMouseOnRight()
}
}
fun isMouseAfterThumb(): Boolean {
return when {
Adjustable.VERTICAL == scrollBar!!.orientation -> isMouseOnBottom()
scrollBar!!.componentOrientation.isLeftToRight -> isMouseOnRight()
Adjustable.VERTICAL == installedState!!.scrollBar.orientation -> isMouseOnBottom()
installedState!!.scrollBar.componentOrientation.isLeftToRight -> isMouseOnRight()
else -> isMouseOnLeft()
}
}
fun isMouseOnTop(): Boolean {
return myMouseY < thumb.bounds.y
}
fun isMouseOnTop(): Boolean = myMouseY < installedState!!.thumb.bounds.y
fun isMouseOnLeft(): Boolean {
return myMouseX < thumb.bounds.x
}
fun isMouseOnLeft(): Boolean = myMouseX < installedState!!.thumb.bounds.x
fun isMouseOnRight(): Boolean {
val thumb = installedState!!.thumb
return myMouseX > thumb.bounds.x + thumb.bounds.width
}
fun isMouseOnBottom(): Boolean {
val thumb = installedState!!.thumb
return myMouseY > thumb.bounds.y + thumb.bounds.height
}
fun scroll(reversed: Boolean) {
var delta: Int = scrollBar!!.getBlockIncrement(if (reversed) -1 else 1)
if (reversed) delta = -delta
val scrollBar = installedState!!.scrollBar
var delta: Int = scrollBar.getBlockIncrement(if (reversed) -1 else 1)
if (reversed) {
delta = -delta
}
val oldValue: Int = scrollBar!!.value
var newValue: Int = oldValue + delta
val oldValue = scrollBar.value
var newValue = oldValue + delta
if (delta > 0 && newValue < oldValue) {
newValue = scrollBar!!.maximum
newValue = scrollBar.maximum
}
else if (delta < 0 && newValue > oldValue) {
newValue = scrollBar!!.minimum
newValue = scrollBar.minimum
}
if (oldValue != newValue) {
scrollBar!!.value = newValue
scrollBar.value = newValue
}
}
}
}
internal class DefaultScrollbarUiInstalledState(ui: DefaultScrollBarUI, @JvmField val scrollBar: JScrollBar) {
@JvmField
val animationBehavior: ScrollBarAnimationBehavior = ui.createWrapAnimationBehaviour(this)
@JvmField
val coroutineScope = CoroutineScope(SupervisorJob() +
Dispatchers.Default +
ModalityState.defaultModalityState().asContextElement() +
ClientId.coroutineContext())
@JvmField
val track: ScrollBarPainter.Track = ScrollBarPainter.Track({ scrollBar }, coroutineScope)
@JvmField
val thumb: ScrollBarPainter.Thumb = ui.createThumbPainter(this)
}
private fun addPreferredWidth(preferred: Dimension, component: Component?) {
if (component != null) {
val size: Dimension = component.preferredSize

View File

@@ -80,8 +80,7 @@ internal open class MacScrollBarUI : DefaultScrollBarUI {
if (event != null && MouseEvent.MOUSE_MOVED == event.id) {
val source = event.source
if (source is Component) {
val pane = ComponentUtil.getParentOfType(
JScrollPane::class.java as Class<out JScrollPane?>, source)
val pane = ComponentUtil.getParentOfType(JScrollPane::class.java, source)
if (pane != null) {
pauseThumbAnimation(pane.horizontalScrollBar)
pauseThumbAnimation(pane.verticalScrollBar)
@@ -97,42 +96,47 @@ internal open class MacScrollBarUI : DefaultScrollBarUI {
*/
private fun pauseThumbAnimation(bar: JScrollBar?) {
val ui = bar?.ui
if (ui is MacScrollBarUI && 0 < ui.animationBehavior!!.thumbFrame) {
ui.animationBehavior!!.onThumbMove()
if (ui is MacScrollBarUI) {
val animationBehavior = ui.installedState!!.animationBehavior
if (0 < animationBehavior.thumbFrame) {
animationBehavior.onThumbMove()
}
}
}
})
}
override fun createBaseAnimationBehavior(): ScrollBarAnimationBehavior {
override fun createBaseAnimationBehavior(state: DefaultScrollbarUiInstalledState): ScrollBarAnimationBehavior {
return MacScrollBarAnimationBehavior(
scrollBarComputable = { scrollBar },
trackAnimator = myTrack.animator,
thumbAnimator = thumb.animator,
scrollBarComputable = { state.scrollBar },
trackAnimator = state.track.animator,
thumbAnimator = state.thumb.animator,
)
}
override fun isAbsolutePositioning(event: MouseEvent): Boolean = Behavior.JumpToSpot == Behavior.CURRENT_BEHAVIOR()
override fun isTrackClickable(): Boolean {
return isOpaque(scrollBar!!) || (animationBehavior!!.trackFrame > 0 && animationBehavior!!.thumbFrame > 0)
val state = installedState ?: return false
return isOpaque(state.scrollBar) || (state.animationBehavior.trackFrame > 0 && state.animationBehavior.thumbFrame > 0)
}
override val isTrackExpandable: Boolean
get() = !isOpaque(scrollBar!!)
get() = !isOpaque(installedState!!.scrollBar)
override fun paintTrack(g: Graphics2D, c: JComponent) {
if (animationBehavior!!.trackFrame > 0 && animationBehavior!!.thumbFrame > 0 || isOpaque(c)) {
val animationBehavior = installedState!!.animationBehavior
if (animationBehavior.trackFrame > 0 && animationBehavior.thumbFrame > 0 || isOpaque(c)) {
super.paintTrack(g, c)
}
}
public override fun paintThumb(g: Graphics2D, c: JComponent) {
public override fun paintThumb(g: Graphics2D, c: JComponent, state: DefaultScrollbarUiInstalledState) {
if (isOpaque(c)) {
paint(p = thumb, g = g, c = c, small = true)
paint(p = state.thumb, g = g, c = c, small = true)
}
else if (animationBehavior!!.thumbFrame > 0) {
paint(p = thumb, g = g, c = c, small = false)
else if (state.animationBehavior.thumbFrame > 0) {
paint(p = state.thumb, g = g, c = c, small = false)
}
}
@@ -152,13 +156,12 @@ internal open class MacScrollBarUI : DefaultScrollBarUI {
}
protected open fun updateStyle(style: MacScrollbarStyle?) {
val scrollBar = scrollBar
if (scrollBar != null) {
scrollBar.isOpaque = style != MacScrollbarStyle.Overlay
scrollBar.revalidate()
scrollBar.repaint()
animationBehavior?.onThumbMove()
}
val state = installedState
val scrollBar = state?.scrollBar ?: return
scrollBar.isOpaque = style != MacScrollbarStyle.Overlay
scrollBar.revalidate()
scrollBar.repaint()
state.animationBehavior.onThumbMove()
}
}

View File

@@ -12,6 +12,7 @@ import com.intellij.ui.JBColor;
import com.intellij.ui.MixedColorProducer;
import com.intellij.ui.paint.RectanglePainter;
import com.intellij.util.ui.RegionPainter;
import kotlinx.coroutines.CoroutineScope;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -130,8 +131,8 @@ public abstract class ScrollBarPainter implements RegionPainter<Float> {
private static final int LIGHT_ALPHA = SystemInfoRt.isMac ? 120 : 160;
private static final int DARK_ALPHA = SystemInfoRt.isMac ? 255 : 180;
ScrollBarPainter(@NotNull Supplier<? extends Component> supplier) {
animator = new TwoWayAnimator(getClass().getName(), 11, 150, 125, 300, 125) {
ScrollBarPainter(@NotNull Supplier<? extends Component> supplier, @NotNull CoroutineScope coroutineScope) {
animator = new TwoWayAnimator(getClass().getName(), 11, 150, 125, 300, 125, coroutineScope) {
@Override
public void onValueUpdate() {
Component component = supplier.get();
@@ -191,8 +192,8 @@ public abstract class ScrollBarPainter implements RegionPainter<Float> {
public static final class Track extends ScrollBarPainter {
private final MixedColorProducer fillProducer;
public Track(@NotNull Supplier<? extends Component> supplier) {
super(supplier);
public Track(@NotNull Supplier<? extends Component> supplier, @NotNull CoroutineScope coroutineScope) {
super(supplier, coroutineScope);
fillProducer = new MixedColorProducer(
getColor(supplier, TRACK_BACKGROUND, TRACK_OPAQUE_BACKGROUND),
getColor(supplier, TRACK_HOVERED_BACKGROUND, TRACK_OPAQUE_HOVERED_BACKGROUND));
@@ -217,8 +218,8 @@ public abstract class ScrollBarPainter implements RegionPainter<Float> {
private final MixedColorProducer fillProducer;
private final MixedColorProducer drawProducer;
Thumb(@NotNull Supplier<? extends Component> supplier, boolean opaque) {
super(supplier);
Thumb(@NotNull Supplier<? extends Component> supplier, boolean opaque, @NotNull CoroutineScope coroutineScope) {
super(supplier, coroutineScope);
fillProducer = new MixedColorProducer(
opaque ? getColor(supplier, THUMB_OPAQUE_BACKGROUND)
: getColor(supplier, THUMB_BACKGROUND, THUMB_OPAQUE_BACKGROUND),
@@ -266,8 +267,8 @@ public abstract class ScrollBarPainter implements RegionPainter<Float> {
@ApiStatus.Internal
public static class ThinScrollBarThumb extends Thumb {
ThinScrollBarThumb(@NotNull Supplier<? extends Component> supplier, boolean opaque) {
super(supplier, opaque);
ThinScrollBarThumb(@NotNull Supplier<? extends Component> supplier, boolean opaque, @NotNull CoroutineScope coroutineScope) {
super(supplier, opaque, coroutineScope);
}
@Override

View File

@@ -14,40 +14,45 @@ internal class TabMacScrollBarUI(
thicknessMin: Int,
) : ThinMacScrollBarUI(thickness, thicknessMax, thicknessMin) {
private var isHovered: Boolean = false
private val defaultColorProducer: MixedColorProducer = MixedColorProducer(
ScrollBarPainter.getColor({ scrollBar }, ScrollBarPainter.TABS_TRANSPARENT_THUMB_BACKGROUND),
ScrollBarPainter.getColor({ scrollBar }, ScrollBarPainter.TABS_THUMB_BACKGROUND))
private val hoveredColorProducer: MixedColorProducer = MixedColorProducer(
ScrollBarPainter.getColor({ scrollBar }, ScrollBarPainter.TABS_THUMB_BACKGROUND),
ScrollBarPainter.getColor({ scrollBar }, ScrollBarPainter.TABS_THUMB_HOVERED_BACKGROUND))
override fun createThumbPainter(): ScrollBarPainter.Thumb {
return object : ThinScrollBarThumb({ scrollBar }, false) {
override fun createThumbPainter(state: DefaultScrollbarUiInstalledState): ScrollBarPainter.Thumb {
val defaultColorProducer = MixedColorProducer(
ScrollBarPainter.getColor({ state.scrollBar }, ScrollBarPainter.TABS_TRANSPARENT_THUMB_BACKGROUND),
ScrollBarPainter.getColor({ state.scrollBar }, ScrollBarPainter.TABS_THUMB_BACKGROUND),
)
val hoveredColorProducer = MixedColorProducer(
ScrollBarPainter.getColor({ state.scrollBar }, ScrollBarPainter.TABS_THUMB_BACKGROUND),
ScrollBarPainter.getColor({ state.scrollBar }, ScrollBarPainter.TABS_THUMB_HOVERED_BACKGROUND),
)
return object : ThinScrollBarThumb({ state.scrollBar }, false, state.coroutineScope) {
override fun getFillProducer() = if (isHovered) hoveredColorProducer else defaultColorProducer
}
}
override fun createWrapAnimationBehaviour(): ScrollBarAnimationBehavior {
return object : ToggleableScrollBarAnimationBehaviorDecorator(createBaseAnimationBehavior(), myTrack.animator, thumb.animator) {
override fun createWrapAnimationBehaviour(state: DefaultScrollbarUiInstalledState): ScrollBarAnimationBehavior {
return object : ToggleableScrollBarAnimationBehaviorDecorator(
decoratedBehavior = createBaseAnimationBehavior(state),
trackAnimator = state.track.animator,
thumbAnimator = state.thumb.animator,
) {
override fun onThumbHover(hovered: Boolean) {
super.onThumbHover(hovered)
if (isHovered != hovered) {
isHovered = hovered
scrollBar!!.revalidate()
scrollBar!!.repaint()
state.scrollBar.revalidate()
state.scrollBar.repaint()
}
}
}
}
override fun paintThumb(g: Graphics2D, c: JComponent) {
if (animationBehavior != null && animationBehavior!!.thumbFrame > 0) {
paint(thumb, g, c, !isHovered)
override fun paintThumb(g: Graphics2D, c: JComponent, state: DefaultScrollbarUiInstalledState) {
if (state.animationBehavior.thumbFrame > 0) {
paint(p = state.thumb, g = g, c = c, small = !isHovered)
}
}
override fun getInsets(small: Boolean): Insets {
return if (small) JBUI.insetsBottom(2) else JBUI.emptyInsets()
}
override fun getInsets(small: Boolean): Insets = if (small) JBUI.insetsBottom(2) else JBUI.emptyInsets()
}

View File

@@ -28,7 +28,7 @@ internal class TabScrollBarUI(
}
}
override fun createWrapAnimationBehaviour(): ScrollBarAnimationBehavior {
override fun createWrapAnimationBehaviour(defaultScrollbarUiInstalledState: DefaultScrollbarUiInstalledState): ScrollBarAnimationBehavior {
return object : ToggleableScrollBarAnimationBehaviorDecorator(createBaseAnimationBehavior(), myTrack.animator, thumb.animator) {
override fun onThumbHover(hovered: Boolean) {
super.onThumbHover(hovered)
@@ -42,7 +42,7 @@ internal class TabScrollBarUI(
}
override fun paintThumb(g: Graphics2D, c: JComponent) {
override fun paintThumb(g: Graphics2D, c: JComponent, state: DefaultScrollbarUiInstalledState) {
if (animationBehavior != null && animationBehavior!!.thumbFrame > 0) {
paint(thumb, g, c, !isHovered)
}

View File

@@ -21,12 +21,12 @@ public class ThinMacScrollBarUI extends MacScrollBarUI {
}
@Override
protected ScrollBarPainter.Thumb createThumbPainter() {
return new ScrollBarPainter.ThinScrollBarThumb(() -> scrollBar, false);
public @NotNull ScrollBarPainter.Thumb createThumbPainter$intellij_platform_ide(@NotNull DefaultScrollbarUiInstalledState state) {
return new ScrollBarPainter.ThinScrollBarThumb(() -> state.scrollBar, false, state.coroutineScope);
}
@Override
public void paintTrack(Graphics2D g, JComponent c) {
public void paintTrack(@NotNull Graphics2D g, @NotNull JComponent c) {
// Track is not needed
}

View File

@@ -18,18 +18,20 @@ internal open class ThinScrollBarUI : DefaultScrollBarUI {
thicknessMin = thicknessMin,
)
override fun createThumbPainter(): ScrollBarPainter.Thumb = ThinScrollBarThumb({ scrollBar }, false)
override fun createThumbPainter(state: DefaultScrollbarUiInstalledState): ScrollBarPainter.Thumb {
return ThinScrollBarThumb({ state.scrollBar }, false, state.coroutineScope)
}
override fun paintTrack(g: Graphics2D, c: JComponent) {
// track is not needed
}
override fun paintThumb(g: Graphics2D, c: JComponent) {
override fun paintThumb(g: Graphics2D, c: JComponent, state: DefaultScrollbarUiInstalledState) {
if (isOpaque(c)) {
paint(p = thumb, g = g, c = c, small = ScrollSettings.isThumbSmallIfOpaque.invoke())
paint(p = state.thumb, g = g, c = c, small = ScrollSettings.isThumbSmallIfOpaque.invoke())
}
else if (animationBehavior != null && animationBehavior!!.thumbFrame > 0) {
paint(p = thumb, g = g, c = c, small = false)
else if (state.animationBehavior.thumbFrame > 0) {
paint(p = state.thumb, g = g, c = c, small = false)
}
}

View File

@@ -19,6 +19,7 @@ abstract class TwoWayAnimator(
durationForward: Int,
pauseBackward: Int,
durationBackward: Int,
private val coroutineScope: CoroutineScope,
) {
private val animateRequests = MutableSharedFlow<MyAnimator>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
@@ -29,6 +30,7 @@ abstract class TwoWayAnimator(
cycleDuration = durationForward,
pauseInMs = pauseForward,
forward = true,
coroutineScope = coroutineScope,
)
}
@@ -39,6 +41,7 @@ abstract class TwoWayAnimator(
cycleDuration = durationBackward,
pauseInMs = pauseBackward,
forward = false,
coroutineScope = coroutineScope,
)
}
@@ -48,16 +51,14 @@ abstract class TwoWayAnimator(
@JvmField
var value: Float = 0f
private var coroutineScope: CoroutineScope? = null
private var job: Job? = null
abstract fun onValueUpdate()
fun start(forward: Boolean) {
@Suppress("SSBasedInspection")
if (coroutineScope == null) {
if (job == null) {
val context = Dispatchers.EDT + ModalityState.defaultModalityState().asContextElement()
coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineName("TwoWayAnimator"))
coroutineScope!!.launch {
job = coroutineScope.launch {
animateRequests.collectLatest { animator ->
delay(animator.pauseInMs.toLong())
withContext(context) {
@@ -96,8 +97,8 @@ abstract class TwoWayAnimator(
}
fun stop() {
coroutineScope?.let {
coroutineScope = null
job?.let {
job = null
it.cancel()
}
suspendAnimation()
@@ -124,12 +125,14 @@ abstract class TwoWayAnimator(
cycleDuration: Int,
@JvmField val pauseInMs: Int,
forward: Boolean,
coroutineScope: CoroutineScope,
) : Animator(
name = name,
totalFrames = totalFrames,
cycleDuration = cycleDuration,
isRepeatable = false,
isForward = forward,
coroutineScope = coroutineScope,
) {
override fun paintNow(frame: Int, totalFrames: Int, cycle: Int) {
if (if (isForward) frame > this@TwoWayAnimator.frame else frame < this@TwoWayAnimator.frame) {

View File

@@ -286,12 +286,17 @@ open class JBTabsImpl internal constructor(
internal val attractions: MutableSet<TabInfo> = HashSet()
private val animator = lazy {
val result = object : Animator("JBTabs Attractions", 2, 500, true) {
val result = object : Animator(
name = "JBTabs Attractions",
totalFrames = 2,
cycleDuration = 500,
isRepeatable = true,
coroutineScope = coroutineScope,
) {
override fun paintNow(frame: Int, totalFrames: Int, cycle: Int) {
repaintAttractions()
}
}
Disposer.register(parentDisposable, result)
result
}
@@ -3029,19 +3034,19 @@ open class JBTabsImpl internal constructor(
for (each in visibleInfos) {
each.changeSupport.firePropertyChange(TabInfo.ACTION_GROUP, "new1", "new2")
}
relayout(true, false)
relayout(forced = true, layoutNow = false)
return this
}
final override fun setSideComponentOnTabs(onTabs: Boolean): JBTabsPresentation {
isSideComponentOnTabs = onTabs
relayout(true, false)
relayout(forced = true, layoutNow = false)
return this
}
final override fun setSideComponentBefore(before: Boolean): JBTabsPresentation {
isSideComponentBefore = before
relayout(true, false)
relayout(forced = true, layoutNow = false)
return this
}
@@ -3137,10 +3142,12 @@ open class JBTabsImpl internal constructor(
private class DefaultDecorator : UiDecorator {
override fun getDecoration(): UiDecoration {
return UiDecoration(labelFont = null,
labelInsets = JBUI.insets(5, 8),
contentInsetsSupplier = java.util.function.Function { JBUI.insets(0, 4) },
iconTextGap = JBUI.scale(4))
return UiDecoration(
labelFont = null,
labelInsets = JBUI.insets(5, 8),
contentInsetsSupplier = { JBUI.insets(0, 4) },
iconTextGap = JBUI.scale(4),
)
}
}

View File

@@ -2,7 +2,6 @@
package com.intellij.util.ui
import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
@@ -14,18 +13,23 @@ import java.awt.GraphicsEnvironment
import javax.swing.SwingUtilities
import kotlin.time.Duration.Companion.microseconds
abstract class Animator @JvmOverloads constructor(private val name: @NonNls String?,
private val totalFrames: Int,
private val cycleDuration: Int,
private val isRepeatable: Boolean,
@JvmField protected val isForward: Boolean = true,
coroutineScope: CoroutineScope? = null) : Disposable {
abstract class Animator @JvmOverloads constructor(
private val name: @NonNls String?,
private val totalFrames: Int,
private val cycleDuration: Int,
private val isRepeatable: Boolean,
@JvmField protected val isForward: Boolean = true,
coroutineScope: CoroutineScope? = null,
) {
private var ticker: Job? = null
private var currentFrame = 0
private var startTime: Long = 0
private var startDeltaTime: Long = 0
private var initialStep = false
private val coroutineScope = coroutineScope ?: CoroutineScope(SupervisorJob() + Dispatchers.Default + ClientId.coroutineContext())
private val coroutineScope = coroutineScope ?: CoroutineScope(SupervisorJob() +
Dispatchers.Default +
ModalityState.defaultModalityState().asContextElement() +
ClientId.coroutineContext())
@Obsolete
fun isForward(): Boolean = isForward
@@ -133,7 +137,7 @@ abstract class Animator @JvmOverloads constructor(private val name: @NonNls Stri
abstract fun paintNow(frame: Int, totalFrames: Int, cycle: Int)
override fun dispose() {
open fun dispose() {
stopTicker()
coroutineScope.cancel()
isDisposed = true

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ui;
import com.intellij.application.Topics;
@@ -95,7 +95,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
private int mySmartFadeoutDelay;
private MyComponent component;
private JLayeredPane myLayeredPane;
private JLayeredPane layeredPane;
private AbstractPosition myPosition;
private Point myTargetPoint;
private final boolean myHideOnFrameResize;
@@ -242,8 +242,8 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
public void setPointerColor(Color pointerColor) {
myPointerColor = pointerColor;
myLayeredPane.revalidate();
myLayeredPane.repaint();
layeredPane.revalidate();
layeredPane.repaint();
}
private final long myFadeoutTime;
@@ -317,7 +317,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
}
}
};
private Animator myAnimator;
private Animator animator;
private boolean myShowPointer;
private boolean isDisposed;
@@ -472,11 +472,11 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
myVisible = true;
myLayeredPane = root.getLayeredPane();
layeredPane = root.getLayeredPane();
myPosition = position;
UIUtil.setFutureRootPane(myContent, root);
myFocusManager = IdeFocusManager.findInstanceByComponent(myLayeredPane);
myFocusManager = IdeFocusManager.findInstanceByComponent(layeredPane);
final Ref<Component> originalFocusOwner = new Ref<>();
final Ref<ActionCallback> proxyFocusRequest = new Ref<>(ActionCallback.DONE);
@@ -497,9 +497,9 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
}
});
}
myLayeredPane.addComponentListener(myComponentListener);
layeredPane.addComponentListener(myComponentListener);
myTargetPoint = myPosition.getShiftedPoint(myTracker.recalculateLocation(this).getPoint(myLayeredPane), myCalloutShift);
myTargetPoint = myPosition.getShiftedPoint(myTracker.recalculateLocation(this).getPoint(layeredPane), myCalloutShift);
if (isDisposed) return; //tracker may dispose the balloon
int positionChangeFix = 0;
@@ -510,11 +510,11 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
if (!myPosition.isOkToHavePointer(myTargetPoint, rec, getPointerLength(myPosition), getPointerWidth(myPosition), getArc())) {
rec = getRecForPosition(myPosition, false);
Rectangle lp = new Rectangle(new Point(myContainerInsets.left, myContainerInsets.top), myLayeredPane.getSize());
Rectangle lp = new Rectangle(new Point(myContainerInsets.left, myContainerInsets.top), layeredPane.getSize());
lp.width -= myContainerInsets.right;
lp.height -= myContainerInsets.bottom;
if (!lp.contains(rec) || !PopupLocationTracker.canRectangleBeUsed(myLayeredPane, rec, this)) {
if (!lp.contains(rec) || !PopupLocationTracker.canRectangleBeUsed(layeredPane, rec, this)) {
Rectangle2D currentSquare = lp.createIntersection(rec);
double maxSquare = currentSquare.getWidth() * currentSquare.getHeight();
@@ -536,7 +536,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
}
if (myPosition != position) {
myTargetPoint = myPosition.getShiftedPoint(myTracker.recalculateLocation(this).getPoint(myLayeredPane),
myTargetPoint = myPosition.getShiftedPoint(myTracker.recalculateLocation(this).getPoint(layeredPane),
myCalloutShift > 0 ? myCalloutShift + positionChangeFix : positionChangeFix);
position = myPosition;
}
@@ -544,18 +544,18 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
createComponent();
Rectangle r = getRecForPosition(myPosition, false);
Point location = r.getLocation();
SwingUtilities.convertPointToScreen(location, myLayeredPane);
SwingUtilities.convertPointToScreen(location, layeredPane);
r.setLocation(location);
if (!PopupLocationTracker.canRectangleBeUsed(myLayeredPane, r, this)) {
if (!PopupLocationTracker.canRectangleBeUsed(layeredPane, r, this)) {
for (AbstractPosition eachPosition : myPosition.getOtherPositions()) {
r = getRecForPosition(eachPosition, false);
location = r.getLocation();
SwingUtilities.convertPointToScreen(location, myLayeredPane);
SwingUtilities.convertPointToScreen(location, layeredPane);
r.setLocation(location);
if (PopupLocationTracker.canRectangleBeUsed(myLayeredPane, r, this)) {
if (PopupLocationTracker.canRectangleBeUsed(layeredPane, r, this)) {
myPosition = eachPosition;
positionChangeFix = myPosition.getChangeShift(position, myPositionChangeXShift, myPositionChangeYShift);
myTargetPoint = myPosition.getShiftedPoint(myTracker.recalculateLocation(this).getPoint(myLayeredPane),
myTargetPoint = myPosition.getShiftedPoint(myTracker.recalculateLocation(this).getPoint(layeredPane),
myCalloutShift > 0 ? myCalloutShift + positionChangeFix : positionChangeFix);
myPosition.updateBounds(this);
break;
@@ -571,10 +571,10 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
!myPosition.isOkToHavePointer(myTargetPoint, rec, getPointerLength(myPosition), getPointerWidth(myPosition), getArc())) {
myShowPointer = false;
component.removeAll();
myLayeredPane.remove(component);
layeredPane.remove(component);
createComponent();
Dimension availSpace = myLayeredPane.getSize();
Dimension availSpace = layeredPane.getSize();
Dimension reqSpace = component.getSize();
if (!new Rectangle(availSpace).contains(new Rectangle(reqSpace))) {
// Balloon is bigger than window, don't show it at all.
@@ -582,8 +582,8 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
"required [" + reqSpace.width + " x " + reqSpace.height + "], " +
"available [" + availSpace.width + " x " + availSpace.height + "]");
component.removeAll();
myLayeredPane.remove(component);
myLayeredPane = null;
layeredPane.remove(component);
layeredPane = null;
hide();
return;
}
@@ -594,11 +594,11 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
}
if (isAnimationEnabled()) {
runAnimation(true, myLayeredPane, null);
runAnimation(true, layeredPane, null);
}
myLayeredPane.revalidate();
myLayeredPane.repaint();
layeredPane.revalidate();
layeredPane.repaint();
if (myRequestFocus) {
myFocusManager.doWhenFocusSettlesDown(new ExpirableRunnable() {
@@ -783,9 +783,9 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
createComponentBorder();
myLayeredPane.add(component);
layeredPane.add(component);
if (myZeroPositionInLayer) {
myLayeredPane.setLayer(component, getLayer(), 0); // the second balloon must be over the first one
layeredPane.setLayer(component, getLayer(), 0); // the second balloon must be over the first one
}
myPosition.updateBounds(this);
@@ -828,14 +828,14 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
public @NotNull Rectangle getConsumedScreenBounds() {
Rectangle bounds = component.getBounds();
Point location = bounds.getLocation();
SwingUtilities.convertPointToScreen(location, myLayeredPane);
SwingUtilities.convertPointToScreen(location, layeredPane);
bounds.setLocation(location);
return bounds;
}
@Override
public Window getUnderlyingWindow() {
return ComponentUtil.getWindow(myLayeredPane);
return ComponentUtil.getWindow(layeredPane);
}
private @NotNull EmptyBorder getPointlessBorder() {
@@ -857,7 +857,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
RelativePoint newPosition = tracker.recalculateLocation(this);
if (newPosition != null) {
Point newPoint = myPosition.getShiftedPoint(newPosition.getPoint(myLayeredPane), myCalloutShift);
Point newPoint = myPosition.getShiftedPoint(newPosition.getPoint(layeredPane), myCalloutShift);
invalidateShadow = !Objects.equals(myTargetPoint, newPoint);
myTargetPoint = newPoint;
myPosition.updateBounds(this);
@@ -918,11 +918,11 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
private void runAnimation(boolean forward, final JLayeredPane layeredPane, final @Nullable Runnable onDone) {
if (myAnimator != null) {
Disposer.dispose(myAnimator);
if (animator != null) {
animator.dispose();
}
myAnimator = new Animator("Balloon", 8, isAnimationEnabled() ? myAnimationCycle : 0, false, forward) {
animator = new Animator("Balloon", 8, isAnimationEnabled() ? myAnimationCycle : 0, false, forward) {
@Override
public void paintNow(final int frame, final int totalFrames, final int cycle) {
if (component == null || component.getParent() == null || !isAnimationEnabled()) return;
@@ -948,20 +948,20 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
layeredPane.revalidate();
layeredPane.repaint();
}
Disposer.dispose(this);
dispose();
}
@Override
public void dispose() {
super.dispose();
myAnimator = null;
animator = null;
if (onDone != null) {
onDone.run();
}
}
};
myAnimator.resume();
animator.resume();
}
void runWithSmartFadeoutPause(@NotNull Runnable handler) {
@@ -1112,14 +1112,14 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
};
Toolkit.getDefaultToolkit().removeAWTEventListener(myAwtActivityListener);
if (myLayeredPane == null) {
if (layeredPane == null) {
disposeRunnable.run();
}
else {
myLayeredPane.removeComponentListener(myComponentListener);
layeredPane.removeComponentListener(myComponentListener);
if (isAnimationEnabled()) {
runAnimation(false, myLayeredPane, disposeRunnable);
runAnimation(false, layeredPane, disposeRunnable);
}
else {
disposeAnimationAndRemoveComponent();
@@ -1132,13 +1132,13 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
}
private void disposeAnimationAndRemoveComponent() {
if (myAnimator != null) {
Disposer.dispose(myAnimator);
if (animator != null) {
animator.dispose();
}
if (component != null) {
myLayeredPane.remove(component);
myLayeredPane.revalidate();
myLayeredPane.repaint();
layeredPane.remove(component);
layeredPane.revalidate();
layeredPane.repaint();
component = null;
}
}
@@ -1218,7 +1218,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
abstract int getChangeShift(AbstractPosition original, int xShift, int yShift);
public void updateBounds(final @NotNull BalloonImpl balloon) {
if (balloon.myLayeredPane == null || balloon.component == null) return;
if (balloon.layeredPane == null || balloon.component == null) return;
Insets shadow = balloon.component.getInsets();
Dimension prefSize = balloon.component.getPreferredSize();
@@ -1237,7 +1237,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
*/
@NotNull
Rectangle getUpdatedBounds(BalloonImpl balloon, Dimension contentSize, Insets shadowInsets) {
Dimension layeredPaneSize = balloon.myLayeredPane.getSize();
Dimension layeredPaneSize = balloon.layeredPane.getSize();
Point point = balloon.myTargetPoint;
Rectangle bounds = balloon.myForcedBounds;
@@ -1955,7 +1955,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
final Graphics2D g2d = (Graphics2D)g;
Point pointTarget = SwingUtilities.convertPoint(myLayeredPane, myBalloon.myTargetPoint, this);
Point pointTarget = SwingUtilities.convertPoint(layeredPane, myBalloon.myTargetPoint, this);
Rectangle shapeBounds = myContent.getBounds();
if (!DrawUtil.isSimplifiedUI()) {
@@ -2008,7 +2008,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
@Override
public boolean contains(int x, int y) {
Point pointTarget = SwingUtilities.convertPoint(myLayeredPane, myBalloon.myTargetPoint, this);
Point pointTarget = SwingUtilities.convertPoint(layeredPane, myBalloon.myTargetPoint, this);
Rectangle bounds = myContent.getBounds();
Shape shape;
if (myShowPointer) {
@@ -2072,14 +2072,14 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
for (ActionButton button : myActionButtons) {
if (button.getParent() == null) {
myLayeredPane.add(button);
myLayeredPane.setLayer(button, JLayeredPane.DRAG_LAYER);
layeredPane.add(button);
layeredPane.setLayer(button, JLayeredPane.DRAG_LAYER);
}
}
}
if (isVisible()) {
Rectangle lpBounds = SwingUtilities.convertRectangle(getParent(), bounds, myLayeredPane);
Rectangle lpBounds = SwingUtilities.convertRectangle(getParent(), bounds, layeredPane);
lpBounds = myPosition
.getPointlessContentRec(lpBounds, myBalloon.myShadowBorderProvider == null ? myBalloon.getPointerLength(myPosition) : 0);
myActionProvider.layout(lpBounds);
@@ -2237,7 +2237,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
@Override
public RelativePoint getShowingPoint() {
Point p = myPosition.getShiftedPoint(myTargetPoint, myCalloutShift * -1);
return new RelativePoint(myLayeredPane, p);
return new RelativePoint(layeredPane, p);
}
@Override

View File

@@ -6,15 +6,14 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.io.FileUtil
import com.intellij.settingsSync.auth.SettingsSyncAuthService
import com.intellij.util.concurrency.SynchronizedClearableLazy
import com.intellij.util.io.HttpRequests
import com.intellij.util.io.delete
import com.intellij.util.resettableLazy
import com.jetbrains.cloudconfig.*
import com.jetbrains.cloudconfig.auth.JbaJwtTokenAuthProvider
import com.jetbrains.cloudconfig.exception.InvalidVersionIdException
import com.jetbrains.cloudconfig.exception.UnauthorizedException
import org.jdom.JDOMException
import org.jetbrains.annotations.TestOnly
import org.jetbrains.annotations.VisibleForTesting
import java.io.FileNotFoundException
import java.io.IOException
@@ -38,8 +37,7 @@ internal open class CloudConfigServerCommunicator(serverUrl: String? = null) : S
@Volatile
internal var _currentIdTokenVar: String? = null
internal var _client = resettableLazy { createCloudConfigClient(serverUrl ?: defaultUrl, clientVersionContext) }
@TestOnly set
private var _client = SynchronizedClearableLazy { createCloudConfigClient(serverUrl ?: defaultUrl, clientVersionContext) }
internal open val client get() = _client.value
private val lastRemoteErrorRef = AtomicReference<Throwable>()
@@ -48,7 +46,7 @@ internal open class CloudConfigServerCommunicator(serverUrl: String? = null) : S
SettingsSyncEvents.getInstance().addListener(
object : SettingsSyncEventListener {
override fun loginStateChanged() {
_client.reset()
_client.drop()
}
}
)
@@ -357,7 +355,7 @@ internal open class CloudConfigServerCommunicator(serverUrl: String? = null) : S
private fun getProductionUrl(): String {
val configUrl = HttpRequests.request(URL_PROVIDER)
.productNameAsUserAgent()
.connect(HttpRequests.RequestProcessor { request: HttpRequests.Request ->
.connect({ request: HttpRequests.Request ->
try {
val documentElement = JDOMUtil.load(request.inputStream)
documentElement.getAttributeValue("baseUrl")