Skip to content

Commit e49dab7

Browse files
add liner gradient selection
1 parent 122e273 commit e49dab7

File tree

9 files changed

+727
-5
lines changed

9 files changed

+727
-5
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.smarttoolfactory.colorpicker.model
2+
3+
import androidx.compose.ui.graphics.Brush
4+
import androidx.compose.ui.graphics.Color
5+
import androidx.compose.ui.graphics.SolidColor
6+
7+
8+
class GradientColor {
9+
val brushColor = BrushColor()
10+
11+
}
12+
13+
class GradientMeta {
14+
15+
}
16+
17+
/**
18+
* Data class that contains [Brush] and [Color] and can return either based on user selection.
19+
*/
20+
data class BrushColor(
21+
var color: Color = Color.Unspecified,
22+
var brush: Brush? = null
23+
) {
24+
/**
25+
* [Brush] that is not **null** [brush] property or [SolidColor] that is not nullable and
26+
* contains [color] property as [SolidColor.value]
27+
*/
28+
val activeBrush: Brush
29+
get() = brush ?: solidColor
30+
31+
/**
32+
* [SolidColor] is a [Brush] that
33+
* wraps [color] property that is used for [activeBrush] if [brush] property is **null**
34+
*/
35+
val solidColor = SolidColor(color)
36+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.smarttoolfactory.colorpicker.selector.gradient
2+
3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.layout.*
6+
import androidx.compose.material.Icon
7+
import androidx.compose.material.Text
8+
import androidx.compose.material.icons.Icons
9+
import androidx.compose.material.icons.filled.ExpandLess
10+
import androidx.compose.material.icons.filled.ExpandMore
11+
import androidx.compose.runtime.*
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.text.font.FontWeight
16+
import androidx.compose.ui.unit.dp
17+
import androidx.compose.ui.unit.sp
18+
19+
@Composable
20+
internal fun ExpandableColumn(
21+
title: String,
22+
color: Color,
23+
content: @Composable () -> Unit
24+
) {
25+
var expanded by remember { mutableStateOf(true) }
26+
Column(modifier = Modifier.fillMaxWidth()) {
27+
28+
Row(
29+
modifier = Modifier
30+
.fillMaxWidth()
31+
.clickable { expanded = !expanded }
32+
.padding(10.dp),
33+
verticalAlignment = Alignment.CenterVertically
34+
) {
35+
Text(
36+
text = title,
37+
fontSize = 16.sp,
38+
color = color,
39+
fontWeight = FontWeight.Bold
40+
)
41+
42+
Spacer(modifier = Modifier.weight(1f))
43+
44+
Icon(
45+
imageVector = if (expanded) Icons.Filled.ExpandLess
46+
else Icons.Filled.ExpandMore,
47+
contentDescription = null,
48+
tint = color
49+
)
50+
}
51+
52+
AnimatedVisibility(visible = expanded) {
53+
content()
54+
}
55+
}
56+
}
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
package com.smarttoolfactory.colorpicker.selector.gradient
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.*
5+
import androidx.compose.foundation.rememberScrollState
6+
import androidx.compose.foundation.shape.RoundedCornerShape
7+
import androidx.compose.foundation.verticalScroll
8+
import androidx.compose.material.*
9+
import androidx.compose.material.icons.Icons
10+
import androidx.compose.material.icons.filled.Close
11+
import androidx.compose.runtime.*
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.draw.clip
15+
import androidx.compose.ui.geometry.Size
16+
import androidx.compose.ui.graphics.Brush
17+
import androidx.compose.ui.graphics.Color
18+
import androidx.compose.ui.graphics.TileMode
19+
import androidx.compose.ui.layout.onSizeChanged
20+
import androidx.compose.ui.unit.dp
21+
import androidx.compose.ui.unit.sp
22+
import com.smarttoolfactory.colorpicker.ui.GradientAngle
23+
import com.smarttoolfactory.colorpicker.ui.GradientOffset
24+
import com.smarttoolfactory.colorpicker.ui.Grey800
25+
import com.smarttoolfactory.colorpicker.ui.Orange400
26+
import com.smarttoolfactory.colorpicker.util.fractionToIntPercent
27+
import com.smarttoolfactory.colorpicker.widget.ExposedSelectionMenu
28+
import com.smarttoolfactory.slider.ColorBrush
29+
import com.smarttoolfactory.slider.ColorfulSlider
30+
import com.smarttoolfactory.slider.MaterialSliderDefaults
31+
import com.smarttoolfactory.slider.ui.InactiveTrackColor
32+
33+
internal val gradientOptions = listOf(
34+
"Linear",
35+
"Radial",
36+
"Sweep"
37+
)
38+
internal val gradientTileModeOptions = listOf("Clamp", "Repeated", "Mirror", "Decal")
39+
40+
@Composable
41+
fun GradientSelector(color: Color, size: Size = Size.Unspecified) {
42+
43+
var gradientSelection by remember { mutableStateOf(0) }
44+
var tileModeSelection by remember { mutableStateOf(0) }
45+
46+
Column(
47+
modifier = Modifier
48+
.fillMaxSize()
49+
.padding(8.dp)
50+
.verticalScroll(rememberScrollState())
51+
) {
52+
ExposedSelectionMenu(
53+
modifier = Modifier.fillMaxWidth(),
54+
index = gradientSelection,
55+
title = "Gradient",
56+
options = gradientOptions,
57+
onSelected = {
58+
gradientSelection = it
59+
}
60+
)
61+
Spacer(modifier = Modifier.height(10.dp))
62+
ExposedSelectionMenu(
63+
modifier = Modifier.fillMaxWidth(),
64+
index = tileModeSelection,
65+
title = "Tile Mode",
66+
options = gradientTileModeOptions,
67+
onSelected = {
68+
tileModeSelection = it
69+
}
70+
)
71+
72+
val tileMode = when (tileModeSelection) {
73+
0 -> TileMode.Clamp
74+
1 -> TileMode.Repeated
75+
2 -> TileMode.Mirror
76+
else -> TileMode.Decal
77+
}
78+
LinearGradientSelection(currentColor = color, tileMode = tileMode)
79+
}
80+
}
81+
82+
@Composable
83+
internal fun LinearGradientSelection(currentColor: Color, tileMode: TileMode) {
84+
85+
val colorStops = remember {
86+
mutableStateListOf(
87+
0.0f to Color.Red,
88+
0.3f to Color.Green,
89+
1.0f to Color.Blue,
90+
)
91+
}
92+
93+
var gradientOffset by remember {
94+
mutableStateOf(
95+
GradientOffset(GradientAngle.CW0)
96+
)
97+
}
98+
99+
val brush = if (colorStops.size == 1) {
100+
val color = colorStops.first().second
101+
Brush.linearGradient(listOf(color, color))
102+
} else {
103+
Brush.linearGradient(
104+
colorStops = colorStops.toTypedArray(),
105+
start = gradientOffset.start,
106+
end = gradientOffset.end,
107+
tileMode = tileMode
108+
)
109+
}
110+
111+
var size by remember {
112+
mutableStateOf(Size.Zero)
113+
}
114+
// Display Brush
115+
Box(
116+
modifier = Modifier
117+
.fillMaxWidth()
118+
.height(150.dp)
119+
.background(brush)
120+
.onSizeChanged {
121+
size = Size(it.width.toFloat(), it.height.toFloat())
122+
}
123+
)
124+
125+
GradientOffsetSelection(
126+
size = size
127+
) { offset ->
128+
gradientOffset = offset
129+
}
130+
131+
ColorStopSelection(
132+
color = currentColor,
133+
colorStops = colorStops,
134+
onRemoveClick = { index: Int ->
135+
if (colorStops.size > 2) {
136+
colorStops.removeAt(index)
137+
}
138+
},
139+
onValueChange = { index: Int, pair: Pair<Float, Color> ->
140+
colorStops[index] = pair.copy()
141+
},
142+
onAddColorStop = { pair: Pair<Float, Color> ->
143+
colorStops.add(pair)
144+
}
145+
)
146+
}
147+
148+
@Composable
149+
internal fun ColorStopSelection(
150+
color: Color,
151+
colorStops: List<Pair<Float, Color>>,
152+
onRemoveClick: (Int) -> Unit,
153+
onAddColorStop: (Pair<Float, Color>) -> Unit,
154+
onValueChange: (Int, Pair<Float, Color>) -> Unit
155+
156+
) {
157+
158+
ExpandableColumn(title = "Colors and Stops", color = Orange400) {
159+
Column {
160+
colorStops.forEachIndexed { index: Int, pair: Pair<Float, Color> ->
161+
GradientColorStopSelection(
162+
index = index,
163+
color = pair.second,
164+
value = pair.first,
165+
onRemoveClick = {
166+
onRemoveClick(it)
167+
168+
},
169+
onValueChange = { newPair: Pair<Float, Color> ->
170+
onValueChange(index, newPair)
171+
println("onValueChange: ")
172+
173+
}
174+
)
175+
}
176+
177+
TextButton(
178+
modifier = Modifier.fillMaxWidth(),
179+
onClick = {
180+
onAddColorStop(Pair(1f, color))
181+
}) {
182+
Text(
183+
modifier = Modifier.padding(4.dp),
184+
text = "Add new Color",
185+
fontSize = 16.sp
186+
)
187+
}
188+
}
189+
}
190+
}
191+
192+
@Composable
193+
private fun GradientColorStopSelection(
194+
index: Int,
195+
color: Color,
196+
value: Float,
197+
onRemoveClick: (Int) -> Unit,
198+
onValueChange: (Pair<Float, Color>) -> Unit
199+
) {
200+
Row(
201+
modifier = Modifier
202+
.fillMaxWidth()
203+
.padding(horizontal = 10.dp, vertical = 2.dp),
204+
verticalAlignment = Alignment.CenterVertically
205+
) {
206+
207+
Box(contentAlignment = Alignment.Center) {
208+
Box(
209+
modifier = Modifier
210+
.clip(RoundedCornerShape(percent = 25))
211+
.background(color)
212+
.size(40.dp)
213+
)
214+
215+
Text(
216+
text = "${value.fractionToIntPercent()}%",
217+
fontSize = 10.sp,
218+
color = Color.White
219+
)
220+
}
221+
Spacer(modifier = Modifier.width(10.dp))
222+
223+
ColorfulSlider(
224+
modifier = Modifier.weight(1f),
225+
value = value,
226+
onValueChange = { value ->
227+
onValueChange(
228+
Pair(value, color)
229+
)
230+
},
231+
thumbRadius = 10.dp,
232+
trackHeight = 8.dp,
233+
colors = MaterialSliderDefaults.materialColors(
234+
inactiveTrackColor = ColorBrush(InactiveTrackColor)
235+
)
236+
)
237+
238+
Spacer(modifier = Modifier.width(10.dp))
239+
FloatingActionButton(
240+
modifier = Modifier
241+
.padding(start = 4.dp, end = 12.dp)
242+
.size(20.dp),
243+
elevation = FloatingActionButtonDefaults.elevation(
244+
defaultElevation = 0.dp
245+
),
246+
backgroundColor = Grey800,
247+
contentColor = Color.White,
248+
onClick = { onRemoveClick(index) }) {
249+
Icon(
250+
imageVector = Icons.Filled.Close,
251+
contentDescription = "close",
252+
)
253+
}
254+
}
255+
}

0 commit comments

Comments
 (0)