Skip to content

Commit 96ed729

Browse files
Merge pull request #93 from AkshayAshokCode/#71
horizontal view added
2 parents 0f738bb + 2cb2dfa commit 96ed729

2 files changed

Lines changed: 181 additions & 101 deletions

File tree

src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt

Lines changed: 161 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,26 @@ import java.awt.Color
1818
import java.awt.Dimension
1919
import java.awt.FlowLayout
2020
import java.awt.Font
21+
import java.awt.GridBagConstraints
22+
import java.awt.GridBagLayout
2123
import java.awt.event.ComponentAdapter
2224
import java.awt.event.ComponentEvent
2325
import javax.swing.*
2426

2527
class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>>(BorderLayout()), Disposable {
2628

27-
private val timerService = project.getService(PomodoroTimerService::class.java) ?: error("PomodoroTimerService not available")
29+
private val timerService = project.getService(PomodoroTimerService::class.java)
30+
?: error("PomodoroTimerService not available")
2831

29-
// Layout orientation tracking
30-
private var isHorizontalLayout = false
32+
private enum class LayoutMode { COMPACT, VERTICAL, HORIZONTAL }
33+
private var currentLayout = LayoutMode.VERTICAL
3134

3235
// Mode selector
3336
private val modeComboBox = JComboBox(PomodoroMode.entries.toTypedArray()).apply {
3437
selectedItem = PomodoroMode.CLASSIC
3538
}
3639

37-
// Setting button
40+
// Settings button
3841
private val settingsButton = JButton(AllIcons.General.Settings).apply {
3942
toolTipText = "Settings"
4043
isBorderPainted = false
@@ -57,13 +60,10 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
5760
private val phaseLabel = JLabel("Focus").apply {
5861
horizontalAlignment = SwingConstants.CENTER
5962
font = font.deriveFont(Font.BOLD, 13f)
60-
foreground = Color(74, 144, 226) // Matches workColor in CircularTimerPanel
63+
foreground = Color(74, 144, 226)
6164
}
6265

63-
// Circular timer display
6466
private val circularTimer = CircularTimerPanel()
65-
66-
// Session indicator with tomato icons
6767
private val sessionIndicator = SessionIndicatorPanel()
6868

6969
// Control buttons
@@ -99,76 +99,193 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
9999
setupLayoutListener()
100100
}
101101

102+
// ---------------------------------------------------------------------------
103+
// Layout routing
104+
// ---------------------------------------------------------------------------
105+
102106
private fun buildUI() {
103-
if(isHorizontalLayout) {
104-
buildHorizontalLayout()
105-
} else {
106-
buildVerticalLayout()
107+
when (currentLayout) {
108+
LayoutMode.COMPACT -> buildCompactLayout()
109+
LayoutMode.VERTICAL -> buildVerticalLayout()
110+
LayoutMode.HORIZONTAL -> buildHorizontalLayout()
111+
}
112+
}
113+
114+
/**
115+
* Compact: either dimension < 160px.
116+
* Just the circular timer + a row of buttons. Everything else hidden.
117+
*/
118+
private fun buildCompactLayout() {
119+
val timerPanel = JPanel(BorderLayout()).apply {
120+
border = BorderFactory.createEmptyBorder(6, 6, 4, 6)
121+
add(circularTimer, BorderLayout.CENTER)
122+
}
123+
val buttonPanel = JPanel(FlowLayout(FlowLayout.CENTER, 4, 2)).apply {
124+
add(startButton)
125+
add(pauseButton)
126+
add(resetButton)
107127
}
128+
add(timerPanel, BorderLayout.CENTER)
129+
add(buttonPanel, BorderLayout.SOUTH)
108130
}
109131

132+
/**
133+
* Vertical: height >= width.
134+
* Mode selector at top. Timer fills all remaining vertical space via
135+
* BorderLayout.CENTER so it grows/shrinks naturally. Controls pinned at bottom.
136+
*
137+
* Scenarios handled:
138+
* - Tall + narrow → small circle (min(width, timerHeight) drives diameter)
139+
* - Tall + wide → large circle (width becomes the constraint)
140+
*/
110141
private fun buildVerticalLayout() {
111-
// Top panel with mode selector
112142
val topPanel = JPanel(BorderLayout(5, 5)).apply {
113-
border = BorderFactory.createEmptyBorder(10, 10, 5, 10)
143+
border = BorderFactory.createEmptyBorder(10, 10, 4, 10)
114144
add(modeComboBox, BorderLayout.CENTER)
115145
add(settingsButton, BorderLayout.EAST)
116146
}
117147

118-
// Info panel
119-
val infoPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 5)).apply {
148+
val infoPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 4)).apply {
120149
add(infoLabel)
121150
}
122151

123-
// Timer panel
152+
// Timer lives in CENTER — it stretches to fill whatever height is left
124153
val timerPanel = JPanel(BorderLayout()).apply {
125-
border = BorderFactory.createEmptyBorder(15, 10, 10, 10)
154+
border = BorderFactory.createEmptyBorder(8, 10, 8, 10)
126155
add(circularTimer, BorderLayout.CENTER)
127156
}
128157

129-
// Session text label panel
130-
val sessionPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 5)).apply {
158+
val phaseLabelPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 2)).apply {
159+
add(phaseLabel)
160+
}
161+
val sessionPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 2)).apply {
131162
add(sessionTextLabel)
132163
}
133-
134-
// Progress panel
135164
val progressPanel = JPanel(BorderLayout(5, 5)).apply {
136-
border = BorderFactory.createEmptyBorder(5, 20, 10, 20)
165+
border = BorderFactory.createEmptyBorder(4, 20, 4, 20)
137166
add(sessionIndicator, BorderLayout.CENTER)
138167
}
139-
140-
// Button panel
141168
val buttonPanel = JPanel(FlowLayout(FlowLayout.CENTER, 8, 5)).apply {
142169
add(startButton)
143170
add(pauseButton)
144171
add(resetButton)
145172
}
146173

147-
// Phase label panel
148-
val phaseLabelPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 2)).apply {
149-
add(phaseLabel)
150-
}
151-
152-
// Center content
153-
val centerPanel = JPanel().apply {
174+
// Fixed-height controls below the timer
175+
val controlsPanel = JPanel().apply {
154176
layout = BoxLayout(this, BoxLayout.Y_AXIS)
155-
add(infoPanel)
156-
add(timerPanel)
157177
add(phaseLabelPanel)
158178
add(sessionPanel)
159179
add(progressPanel)
160180
add(buttonPanel)
161181
}
162182

183+
val centerPanel = JPanel(BorderLayout()).apply {
184+
add(infoPanel, BorderLayout.NORTH)
185+
add(timerPanel, BorderLayout.CENTER) // ← grows with panel
186+
add(controlsPanel, BorderLayout.SOUTH)
187+
}
188+
163189
add(topPanel, BorderLayout.NORTH)
164190
add(centerPanel, BorderLayout.CENTER)
165191
add(settingsPanel, BorderLayout.SOUTH)
166192
}
167193

194+
/**
195+
* Horizontal: width > height.
196+
* Mode selector spans the top. Timer takes left 55%, controls take right 45%.
197+
*
198+
* Scenarios handled:
199+
* - Wide + tall → large circle (height drives diameter), ample control space
200+
* - Wide + short → smaller circle, controls stack compactly on the right
201+
*/
168202
private fun buildHorizontalLayout() {
169-
buildVerticalLayout()
203+
val topPanel = JPanel(BorderLayout(5, 5)).apply {
204+
border = BorderFactory.createEmptyBorder(8, 10, 4, 10)
205+
add(modeComboBox, BorderLayout.CENTER)
206+
add(settingsButton, BorderLayout.EAST)
207+
}
208+
209+
val timerPanel = JPanel(BorderLayout()).apply {
210+
border = BorderFactory.createEmptyBorder(8, 12, 8, 6)
211+
add(circularTimer, BorderLayout.CENTER)
212+
}
213+
214+
val phaseLabelPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 2)).apply { add(phaseLabel) }
215+
val sessionPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 2)).apply { add(sessionTextLabel) }
216+
val progressPanel = JPanel(BorderLayout(5, 5)).apply {
217+
border = BorderFactory.createEmptyBorder(4, 8, 4, 8)
218+
add(sessionIndicator, BorderLayout.CENTER)
219+
}
220+
val buttonPanel = JPanel(FlowLayout(FlowLayout.CENTER, 6, 4)).apply {
221+
add(startButton)
222+
add(pauseButton)
223+
add(resetButton)
224+
}
225+
226+
// Controls centered vertically on the right side
227+
val rightPanel = JPanel().apply {
228+
layout = BoxLayout(this, BoxLayout.Y_AXIS)
229+
border = BorderFactory.createEmptyBorder(4, 4, 4, 12)
230+
add(Box.createVerticalGlue())
231+
add(phaseLabelPanel)
232+
add(sessionPanel)
233+
add(progressPanel)
234+
add(buttonPanel)
235+
add(Box.createVerticalGlue())
236+
}
237+
238+
// Split: timer 55% | controls 45%
239+
val splitPanel = JPanel(GridBagLayout()).apply {
240+
val gbc = GridBagConstraints().apply {
241+
fill = GridBagConstraints.BOTH
242+
weighty = 1.0
243+
}
244+
gbc.weightx = 0.55; gbc.gridx = 0; add(timerPanel, gbc)
245+
gbc.weightx = 0.45; gbc.gridx = 1; add(rightPanel, gbc)
246+
}
247+
248+
add(topPanel, BorderLayout.NORTH)
249+
add(splitPanel, BorderLayout.CENTER)
250+
add(settingsPanel, BorderLayout.SOUTH)
251+
}
252+
253+
// ---------------------------------------------------------------------------
254+
// Responsive layout detection
255+
// ---------------------------------------------------------------------------
256+
257+
private fun setupLayoutListener() {
258+
addComponentListener(object : ComponentAdapter() {
259+
override fun componentResized(e: ComponentEvent?) {
260+
checkAndUpdateLayout()
261+
}
262+
})
263+
}
264+
265+
private fun checkAndUpdateLayout() {
266+
val newLayout = when {
267+
width < 160 || height < 160 -> LayoutMode.COMPACT
268+
width > height -> LayoutMode.HORIZONTAL
269+
else -> LayoutMode.VERTICAL
270+
}
271+
if (newLayout != currentLayout) {
272+
currentLayout = newLayout
273+
rebuildLayout()
274+
}
275+
}
276+
277+
private fun rebuildLayout() {
278+
removeAll()
279+
buildUI()
280+
updateSettingsPanelVisibility()
281+
revalidate()
282+
repaint()
170283
}
171284

285+
// ---------------------------------------------------------------------------
286+
// Listeners & helpers
287+
// ---------------------------------------------------------------------------
288+
172289
private fun setupListeners() {
173290
startButton.addActionListener { timerService.start() }
174291
pauseButton.addActionListener { timerService.pause() }
@@ -191,7 +308,8 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
191308

192309
private fun updateSettingsPanelVisibility() {
193310
val isCustom = modeComboBox.selectedItem == PomodoroMode.CUSTOM
194-
settingsPanel.isVisible = isCustom
311+
// Never show the custom settings panel in compact mode — no room for it
312+
settingsPanel.isVisible = isCustom && currentLayout != LayoutMode.COMPACT
195313
revalidate()
196314
repaint()
197315
}
@@ -206,35 +324,9 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
206324
sessionTextLabel.text = "Session $currentSession of $totalSessions"
207325
}
208326

209-
private fun setupLayoutListener() {
210-
addComponentListener(object : ComponentAdapter() {
211-
override fun componentResized(e: ComponentEvent?) {
212-
checkAndUpdateLayout()
213-
}
214-
})
215-
}
216-
217-
private fun checkAndUpdateLayout() {
218-
val width = width
219-
val height = height
220-
221-
// Determine if we should use horizontal layout (width > height * 1.5)
222-
val shouldBeHorizontal = width > height * 1.5
223-
224-
// Only rebuild if layout orientation changed
225-
if (shouldBeHorizontal != isHorizontalLayout) {
226-
isHorizontalLayout = shouldBeHorizontal
227-
rebuildLayout()
228-
}
229-
}
230-
231-
private fun rebuildLayout() {
232-
removeAll()
233-
buildUI()
234-
updateSettingsPanelVisibility()
235-
revalidate()
236-
repaint()
237-
}
327+
// ---------------------------------------------------------------------------
328+
// Timer observation
329+
// ---------------------------------------------------------------------------
238330

239331
private fun observeTimer() {
240332
timeJob = scope.launch {
@@ -254,18 +346,15 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
254346
pauseButton.isEnabled = it == PomodoroTimerService.TimerState.RUNNING
255347
resetButton.isEnabled = it != PomodoroTimerService.TimerState.IDLE
256348

257-
// Update start button text based on state
258349
startButton.text = when (it) {
259350
PomodoroTimerService.TimerState.IDLE -> "Start"
260351
else -> "Resume"
261352
}
262353

263-
// Clear all default values
264354
startButton.putClientProperty("JButton.buttonType", null)
265355
pauseButton.putClientProperty("JButton.buttonType", null)
266356
resetButton.putClientProperty("JButton.buttonType", null)
267357

268-
// Update default button (blue highlight) based on state
269358
when (it) {
270359
PomodoroTimerService.TimerState.IDLE -> {
271360
startButton.putClientProperty("JButton.buttonType", "default")
@@ -281,7 +370,6 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
281370
}
282371
}
283372

284-
// Check if we're truly idle (session and work phase) or just transitioning
285373
val currentSession = timerService.currentSession.value
286374
val currentPhase = timerService.currentPhase.value
287375
val isTrulyIdle = it == PomodoroTimerService.TimerState.IDLE &&
@@ -290,12 +378,11 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
290378

291379
modeComboBox.isEnabled = isTrulyIdle
292380

293-
// Hide custom settings panel when timer is active
294381
if (!isTrulyIdle && modeComboBox.selectedItem == PomodoroMode.CUSTOM) {
295382
settingsPanel.isVisible = false
296383
revalidate()
297384
repaint()
298-
} else if (isTrulyIdle){
385+
} else if (isTrulyIdle) {
299386
updateSettingsPanelVisibility()
300387
}
301388
}
@@ -322,10 +409,10 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
322409
sessionIndicator.updateSessions(session, settings.sessionsPerRound, isBreak)
323410
if (isBreak) {
324411
phaseLabel.text = "Break"
325-
phaseLabel.foreground = Color(243, 156, 18) // Matches breakColor
412+
phaseLabel.foreground = Color(243, 156, 18)
326413
} else {
327414
phaseLabel.text = "Focus"
328-
phaseLabel.foreground = Color(74, 144, 226) // Matches workColor
415+
phaseLabel.foreground = Color(74, 144, 226)
329416
}
330417
}
331418
}
@@ -339,4 +426,4 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
339426
phaseJob?.cancel()
340427
scope.cancel()
341428
}
342-
}
429+
}

0 commit comments

Comments
 (0)