Skip to content

Commit d3d9feb

Browse files
authored
feat: Add search function to license viewer (MorpheApp#124)
1 parent 427b21a commit d3d9feb

7 files changed

Lines changed: 921 additions & 47 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"hash": "7fcfcc3d264c3c96aee27ca4c3031c95",
3+
"name": "BSD-3-Clause"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"hash": "d77d6c7c59b08734d0e3482d06219165",
3+
"name": "BSD-3-Clause"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"hash": "177d886f25fda88a964321e0342c9ac6",
3+
"name": "BSD-2-Clause"
4+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"hash": "73252b46f36df25ef51a7994de439aea",
3+
"name": "Bouncy Castle Licence",
4+
"url": "https://www.bouncycastle.org/licence.html",
5+
"content": "Please note the Bouncy Castle License should be read in the same way as the MIT license.\n\nPlease also note this licensing model is made possible through funding from donations and the sale of support contracts.\n\nBouncy Castle License\n\nCopyright (c) 2000 - 2024 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
6+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright 2026 Morphe.
3+
* https://github.com/MorpheApp/morphe-cli
4+
*/
5+
6+
package app.morphe.gui.ui.components
7+
8+
import androidx.compose.animation.animateColorAsState
9+
import androidx.compose.animation.core.Spring
10+
import androidx.compose.animation.core.animateDpAsState
11+
import androidx.compose.animation.core.spring
12+
import androidx.compose.animation.core.tween
13+
import androidx.compose.foundation.background
14+
import androidx.compose.foundation.border
15+
import androidx.compose.foundation.clickable
16+
import androidx.compose.foundation.hoverable
17+
import androidx.compose.foundation.interaction.MutableInteractionSource
18+
import androidx.compose.foundation.interaction.collectIsHoveredAsState
19+
import androidx.compose.foundation.layout.Box
20+
import androidx.compose.foundation.layout.Row
21+
import androidx.compose.foundation.layout.fillMaxSize
22+
import androidx.compose.foundation.layout.offset
23+
import androidx.compose.foundation.layout.size
24+
import androidx.compose.foundation.shape.CircleShape
25+
import androidx.compose.foundation.shape.RoundedCornerShape
26+
import androidx.compose.material3.MaterialTheme
27+
import androidx.compose.material3.Text
28+
import androidx.compose.runtime.Composable
29+
import androidx.compose.runtime.getValue
30+
import androidx.compose.runtime.remember
31+
import androidx.compose.ui.Alignment
32+
import androidx.compose.ui.Modifier
33+
import androidx.compose.ui.draw.clip
34+
import androidx.compose.ui.graphics.Color
35+
import androidx.compose.ui.graphics.graphicsLayer
36+
import androidx.compose.ui.text.font.FontWeight
37+
import androidx.compose.ui.unit.dp
38+
import androidx.compose.ui.unit.sp
39+
import app.morphe.gui.ui.theme.LocalMorpheCorners
40+
import app.morphe.gui.ui.theme.LocalMorpheFont
41+
42+
/**
43+
* Theme-aware toggle. Branches on [LocalMorpheCorners]: sharp themes
44+
* (Dark/Amoled/Light/Nord/Catppuccin) get a rectangular mono-labelled
45+
* terminal switch, soft themes (Sakura/Matcha) get a spring-animated pill.
46+
*/
47+
@Composable
48+
fun MorpheSwitch(
49+
checked: Boolean,
50+
onCheckedChange: (Boolean) -> Unit,
51+
accentColor: Color,
52+
enabled: Boolean = true,
53+
) {
54+
val isSoft = LocalMorpheCorners.current.medium >= 10.dp
55+
if (isSoft) {
56+
SoftMorpheSwitch(checked, onCheckedChange, accentColor, enabled)
57+
} else {
58+
SharpMorpheSwitch(checked, onCheckedChange, accentColor, enabled)
59+
}
60+
}
61+
62+
@Composable
63+
private fun SharpMorpheSwitch(
64+
checked: Boolean,
65+
onCheckedChange: (Boolean) -> Unit,
66+
accentColor: Color,
67+
enabled: Boolean,
68+
) {
69+
val mono = LocalMorpheFont.current
70+
val hover = remember { MutableInteractionSource() }
71+
val isHovered by hover.collectIsHoveredAsState()
72+
val disabledAlpha = if (enabled) 1f else 0.4f
73+
74+
val width = 58.dp
75+
val height = 24.dp
76+
val blockWidth = 28.dp
77+
78+
val blockOffset by animateDpAsState(
79+
targetValue = if (checked) width - blockWidth else 0.dp,
80+
animationSpec = tween(180)
81+
)
82+
val blockColor by animateColorAsState(
83+
targetValue = if (checked) accentColor
84+
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.18f),
85+
animationSpec = tween(180)
86+
)
87+
val borderColor by animateColorAsState(
88+
targetValue = when {
89+
checked -> accentColor.copy(alpha = 0.45f)
90+
isHovered -> MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
91+
else -> MaterialTheme.colorScheme.outline.copy(alpha = 0.18f)
92+
},
93+
animationSpec = tween(180)
94+
)
95+
96+
val mutedLabel = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.35f)
97+
val onBlockLabel = MaterialTheme.colorScheme.onPrimary
98+
99+
Box(
100+
modifier = Modifier
101+
.size(width, height)
102+
.hoverable(hover)
103+
.border(1.dp, borderColor)
104+
.clickable(enabled = enabled) { onCheckedChange(!checked) }
105+
.graphicsLayer { alpha = disabledAlpha }
106+
) {
107+
Box(
108+
modifier = Modifier
109+
.offset(x = blockOffset)
110+
.size(blockWidth, height)
111+
.background(blockColor)
112+
)
113+
Row(modifier = Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
114+
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
115+
Text(
116+
text = "OFF",
117+
fontFamily = mono,
118+
fontSize = 8.sp,
119+
fontWeight = FontWeight.Bold,
120+
letterSpacing = 1.4.sp,
121+
color = if (!checked) onBlockLabel else mutedLabel
122+
)
123+
}
124+
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
125+
Text(
126+
text = "ON",
127+
fontFamily = mono,
128+
fontSize = 8.sp,
129+
fontWeight = FontWeight.Bold,
130+
letterSpacing = 1.4.sp,
131+
color = if (checked) onBlockLabel else mutedLabel
132+
)
133+
}
134+
}
135+
}
136+
}
137+
138+
@Composable
139+
private fun SoftMorpheSwitch(
140+
checked: Boolean,
141+
onCheckedChange: (Boolean) -> Unit,
142+
accentColor: Color,
143+
enabled: Boolean,
144+
) {
145+
val hover = remember { MutableInteractionSource() }
146+
val isHovered by hover.collectIsHoveredAsState()
147+
val disabledAlpha = if (enabled) 1f else 0.4f
148+
149+
val width = 46.dp
150+
val height = 26.dp
151+
val thumbSize = 20.dp
152+
val padding = 3.dp
153+
154+
val thumbOffset by animateDpAsState(
155+
targetValue = if (checked) width - thumbSize - padding else padding,
156+
animationSpec = spring(
157+
dampingRatio = 0.62f,
158+
stiffness = Spring.StiffnessMedium
159+
)
160+
)
161+
val trackColor by animateColorAsState(
162+
targetValue = if (checked) accentColor.copy(alpha = 0.9f)
163+
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
164+
animationSpec = tween(220)
165+
)
166+
val thumbRing by animateColorAsState(
167+
targetValue = when {
168+
checked && isHovered -> accentColor.copy(alpha = 0.35f)
169+
checked -> accentColor.copy(alpha = 0.2f)
170+
isHovered -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.15f)
171+
else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.08f)
172+
},
173+
animationSpec = tween(200)
174+
)
175+
176+
val pillShape = RoundedCornerShape(height / 2)
177+
Box(
178+
modifier = Modifier
179+
.size(width, height)
180+
.hoverable(hover)
181+
.clip(pillShape)
182+
.background(trackColor, pillShape)
183+
.clickable(enabled = enabled) { onCheckedChange(!checked) }
184+
.graphicsLayer { alpha = disabledAlpha }
185+
) {
186+
Box(
187+
modifier = Modifier
188+
.offset(x = thumbOffset, y = padding)
189+
.size(thumbSize)
190+
.background(MaterialTheme.colorScheme.surface, CircleShape)
191+
.border(1.5.dp, thumbRing, CircleShape)
192+
)
193+
}
194+
}

0 commit comments

Comments
 (0)