├── .idea
├── .name
├── .gitignore
├── compiler.xml
├── vcs.xml
├── gradle.xml
├── misc.xml
└── jarRepositories.xml
├── segmenteddisplay
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── rabross
│ │ │ └── segmenteddisplay
│ │ │ ├── seven
│ │ │ ├── SegmentStyle.kt
│ │ │ ├── Decoder.kt
│ │ │ ├── Animations.kt
│ │ │ └── Composable.kt
│ │ │ ├── delimiter
│ │ │ ├── Decoder.kt
│ │ │ └── Composable.kt
│ │ │ ├── Led.kt
│ │ │ └── fourteen
│ │ │ ├── Decoder.kt
│ │ │ └── Composable.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
├── build.gradle
└── .gitignore
├── jitpack.yml
├── app
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ ├── drawable
│ │ │ │ ├── bitmap.png
│ │ │ │ ├── color_bitmap.png
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── rabross
│ │ │ └── segmenteddisplay
│ │ │ └── app
│ │ │ ├── SampleIndexActivity.kt
│ │ │ ├── TypingActivity.kt
│ │ │ ├── SevenSegmentHeartActivity.kt
│ │ │ ├── SevenSegmentActivity.kt
│ │ │ ├── FourteenSegmentActivity.kt
│ │ │ └── SevenSegmentScreenActivity.kt
│ └── test
│ │ └── java
│ │ └── com
│ │ └── rabross
│ │ └── segmenteddisplay
│ │ └── app
│ │ └── ByteBufferSevenSegmentIndexMapperTests.kt
├── proguard-rules.pro
├── build.gradle
└── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── LICENSE
├── gradle.properties
├── README.md
├── gradlew.bat
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | SegmentDisplay
--------------------------------------------------------------------------------
/segmenteddisplay/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
3 | install:
4 | - ./gradlew build :segmenteddisplay:publishToMavenLocal
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SegmentedDisplay
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bitmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/drawable/bitmap.png
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | /inspectionProfiles/
5 | /deploymentTargetDropDown.xml
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/color_bitmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/drawable/color_bitmap.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabross/SegmentedDisplay/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/seven/SegmentStyle.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.seven
2 |
3 | enum class SegmentStyle {
4 | FLAT, DIFFUSER
5 | }
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Sep 16 09:55:56 BST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/delimiter/Decoder.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.delimiter
2 |
3 | interface Decoder {
4 | val a: Int
5 | val b: Int
6 | }
7 |
8 | class BinaryDecoder(data: Int = 0) : Decoder {
9 | override val a = data and 0b01
10 | override val b = data and 0b10
11 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | jcenter() // Warning: this repository is going to shut down soon
7 | }
8 | }
9 | rootProject.name = "SegmentDisplay"
10 | include ':app'
11 | include ':segmenteddisplay'
12 |
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/Led.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | fun interface Led {
6 | fun signal(value: Int): Color
7 | }
8 |
9 | class SingleColorLed(private val on: Color, private val off: Color) : Led {
10 | override fun signal(value: Int) = if (value > 0) on else off
11 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/segmenteddisplay/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Rab Ross
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/seven/Decoder.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.seven
2 |
3 | interface Decoder {
4 | val a: Int
5 | val b: Int
6 | val c: Int
7 | val d: Int
8 | val e: Int
9 | val f: Int
10 | val g: Int
11 | }
12 |
13 | class BinaryDecoder(data: Int = 0) : Decoder {
14 | override val a = data and 0b00000001
15 | override val b = data and 0b00000010
16 | override val c = data and 0b00000100
17 | override val d = data and 0b00001000
18 | override val e = data and 0b00010000
19 | override val f = data and 0b00100000
20 | override val g = data and 0b01000000
21 |
22 | companion object {
23 | fun mapToDisplay(number: Int): Int {
24 | return when (number) {
25 | 0 -> 0b00111111
26 | 1 -> 0b00000110
27 | 2 -> 0b01011011
28 | 3 -> 0b01001111
29 | 4 -> 0b01100110
30 | 5 -> 0b01101101
31 | 6 -> 0b01111101
32 | 7 -> 0b00000111
33 | 8 -> 0b01111111
34 | 9 -> 0b01100111
35 | else -> 0b00000000
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://jitpack.io/#rabross/SegmentedDisplay)
2 | 
3 |
4 | # SegmentedDisplay
5 | A 7-segment and 14-segment display for Android built with Jetpack Compose
6 |
7 |
8 |
9 |
10 |
11 | Inspired by [limpfish](https://www.plingboot.com/oldskool-demo-on-a-7-segment-display/)
12 |
13 | ## Customizable
14 | Highly customisable.
15 |
16 |
17 |
18 | ## Dependency
19 |
20 | implementation 'com.github.rabross:SegmentedDisplay:0.4.0'
21 |
22 | Add the JitPack repository to your root `build.gradle` file
23 |
24 | allprojects {
25 | repositories {
26 | ...
27 | maven { url 'https://jitpack.io' }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rabross/segmenteddisplay/app/SampleIndexActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.app
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.material.Button
11 | import androidx.compose.material.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.unit.dp
16 |
17 | class SampleIndexActivity : ComponentActivity() {
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 |
21 | setContent {
22 | Column {
23 | LaunchButton(text = "7-segment display")
24 | LaunchButton(text = "7-segment display heart")
25 | LaunchButton(text = "14-segment display")
26 | LaunchButton(text = "14-segment display typing")
27 | LaunchButton(text = "Displaying an image")
28 | }
29 | }
30 | }
31 |
32 | @Composable
33 | inline fun LaunchButton(text: String) {
34 | val context = LocalContext.current
35 | Button(modifier = Modifier
36 | .fillMaxWidth()
37 | .padding(16.dp),
38 | onClick = { context.startActivity(Intent(context, T::class.java)) }) {
39 | Text(text = text)
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdk 30
8 |
9 | defaultConfig {
10 | applicationId "com.rabross.segmenteddisplay"
11 | minSdk 21
12 | targetSdk 30
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | vectorDrawables {
18 | useSupportLibrary true
19 | }
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility JavaVersion.VERSION_1_8
30 | targetCompatibility JavaVersion.VERSION_1_8
31 | }
32 | kotlinOptions {
33 | jvmTarget = '1.8'
34 | useIR = true
35 | }
36 | buildFeatures {
37 | compose true
38 | }
39 | composeOptions {
40 | kotlinCompilerExtensionVersion compose_version
41 | kotlinCompilerVersion '1.5.10'
42 | }
43 | packagingOptions {
44 | resources {
45 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
46 | }
47 | }
48 | }
49 |
50 | dependencies {
51 |
52 | implementation project(path: ':segmenteddisplay')
53 | implementation 'androidx.core:core-ktx:1.6.0'
54 | implementation 'androidx.appcompat:appcompat:1.3.1'
55 | implementation 'com.google.android.material:material:1.4.0'
56 | implementation "androidx.compose.ui:ui:$compose_version"
57 | implementation "androidx.compose.material:material:$compose_version"
58 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
59 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
60 | implementation 'androidx.activity:activity-compose:1.3.1'
61 | testImplementation 'junit:junit:4.13.2'
62 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
63 | }
--------------------------------------------------------------------------------
/segmenteddisplay/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'maven-publish'
5 | }
6 |
7 | android {
8 | compileSdk 30
9 |
10 | defaultConfig {
11 | minSdk 21
12 | targetSdk 30
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
17 | consumerProguardFiles "consumer-rules.pro"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_11
28 | targetCompatibility JavaVersion.VERSION_11
29 | }
30 | kotlinOptions {
31 | jvmTarget = '11'
32 | useIR = true
33 | }
34 | buildFeatures {
35 | compose true
36 | }
37 | composeOptions {
38 | kotlinCompilerExtensionVersion compose_version
39 | kotlinCompilerVersion '1.5.10'
40 | }
41 | }
42 |
43 | dependencies {
44 |
45 | implementation 'androidx.core:core-ktx:1.6.0'
46 | implementation 'androidx.appcompat:appcompat:1.3.1'
47 | implementation 'com.google.android.material:material:1.4.0'
48 | implementation "androidx.compose.ui:ui:$compose_version"
49 | implementation "androidx.compose.material:material:$compose_version"
50 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
51 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
52 | implementation 'androidx.activity:activity-compose:1.3.1'
53 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
54 | }
55 |
56 | task androidSourcesJar(type: Jar) {
57 | classifier 'sources'
58 | from android.sourceSets.main.java.srcDirs
59 | }
60 |
61 | project.afterEvaluate {
62 | publishing {
63 | publications {
64 | release(MavenPublication) {
65 | from components.release
66 | artifact androidSourcesJar
67 | }
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/delimiter/Composable.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.delimiter
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.material.Surface
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.geometry.Offset
9 | import androidx.compose.ui.geometry.Size
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.drawscope.DrawScope
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import androidx.compose.ui.unit.dp
14 | import com.rabross.segmenteddisplay.Led
15 | import com.rabross.segmenteddisplay.SingleColorLed
16 |
17 | @Preview
18 | @Composable
19 | fun DelimiterPreview() {
20 | Surface {
21 | Delimiter()
22 | }
23 | }
24 |
25 | @Composable
26 | fun Delimiter(
27 | modifier: Modifier = Modifier,
28 | segmentScale: Int = 3,
29 | led: Led = SingleColorLed(Color.Red, Color.DarkGray.copy(alpha = 0.3f)),
30 | decoder: Decoder = BinaryDecoder()
31 | ) {
32 | Canvas(
33 | modifier = modifier
34 | .aspectRatio(((1f + segmentScale + 1f)) / (1f + segmentScale + 1f + segmentScale + 1f), true)
35 | .size(100.dp)
36 | ) {
37 | val scaleWidth = 1 + segmentScale + 1
38 | val scaleHeight = 1 + segmentScale + 1 + segmentScale + 1
39 |
40 | val segmentWidthByWidth = size.width / scaleWidth
41 | val segmentWidthByHeight = size.height / scaleHeight
42 |
43 | val segmentWidth = if (scaleHeight * segmentWidthByWidth < size.height) segmentWidthByWidth else segmentWidthByHeight
44 |
45 | val totalWidth = segmentWidth * scaleWidth
46 | val totalHeight = segmentWidth * scaleHeight
47 |
48 | drawDelimiter(
49 | led.signal(decoder.a),
50 | led.signal(decoder.b),
51 | segmentWidth / 2,
52 | Offset(0f, segmentWidth / 2),
53 | Size(totalWidth, totalHeight - segmentWidth)
54 | )
55 | }
56 | }
57 |
58 | private fun DrawScope.drawDelimiter(upDotColor: Color, downDotColor: Color, radius: Float, offset: Offset, size: Size) {
59 | val upDotCenterOffset = Offset(size.width / 2 + offset.x, size.height / 4 + offset.y)
60 | val downDotCenterOffset = Offset(size.width / 2 + offset.x, size.height / 4 * 3 + offset.y)
61 | drawCircle(upDotColor, radius, upDotCenterOffset)
62 | drawCircle(downDotColor, radius, downDotCenterOffset)
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rabross/segmenteddisplay/app/TypingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.app
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material.Surface
8 | import androidx.compose.material.Text
9 | import androidx.compose.material.TextField
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.unit.dp
17 | import com.rabross.segmenteddisplay.fourteen.BinaryDecoder
18 | import com.rabross.segmenteddisplay.fourteen.SegmentDisplay
19 |
20 | class TypingActivity : ComponentActivity() {
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContent {
25 |
26 | var text by remember { mutableStateOf("Hello World") }
27 |
28 | Column {
29 | Surface(
30 | modifier = Modifier
31 | .weight(1f), color = Color.Black
32 | ) {
33 | Row(modifier = Modifier.padding(24.dp, 24.dp, 24.dp, 0.dp)) {
34 | if (text.isBlank()) {
35 | Spacer(modifier = Modifier.weight(1f))
36 | } else {
37 | for (c in text) {
38 | SegmentDisplay(
39 | modifier = Modifier.weight(1f),
40 | decoder = BinaryDecoder(
41 | BinaryDecoder.mapToDisplay(c.uppercaseChar())
42 | )
43 | )
44 | }
45 | }
46 | }
47 | }
48 | Row(
49 | Modifier
50 | .weight(2f)
51 | .fillMaxWidth()
52 | .padding(24.dp, 0.dp)
53 | ) {
54 | TextField(modifier = Modifier.fillMaxWidth(),
55 | value = text,
56 | onValueChange = { text = it },
57 | label = { Text("input") }
58 | )
59 | }
60 |
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rabross/segmenteddisplay/app/SevenSegmentHeartActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.app
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.material.Surface
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.unit.dp
13 | import com.rabross.segmenteddisplay.seven.BinaryDecoder
14 | import com.rabross.segmenteddisplay.seven.SegmentDisplay
15 |
16 | class SevenSegmentHeartActivity : ComponentActivity() {
17 |
18 | private val rows = 6
19 | private val columns = 13
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContent {
24 |
25 | Surface(color = Color.Black) {
26 | Column(modifier = Modifier.padding(24.dp)) {
27 | for (row in 0 until rows) {
28 | Row {
29 | for (column in 0 until columns) {
30 | SegmentDisplay(
31 | modifier = Modifier
32 | .weight(1f)
33 | .padding(2.dp),
34 | decoder = BinaryDecoder(buffer[(row * columns) + column])
35 | )
36 | }
37 | }
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
44 | private val buffer = listOf(
45 | 0b0000000, 0b0000100, 0b1011110, 0b1111111, 0b1111111, 0b1111110, 0b0010000, 0b0000100, 0b1111110, 0b1111111, 0b1111110, 0b1011100, 0b0000000,
46 | 0b0000100, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111101, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111101,
47 | 0b0000010, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111011,
48 | 0b0000000, 0b0000010, 0b1100111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1111111, 0b1100011, 0b0000000,
49 | 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0100011, 0b1110111, 0b1111111, 0b1111111, 0b1110011, 0b0100001, 0b0000000, 0b0000000, 0b0000000,
50 | 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b1100111, 0b0100001, 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0000000
51 | )
52 | }
--------------------------------------------------------------------------------
/segmenteddisplay/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | ### Gradle template
3 | .gradle
4 | **/build/
5 | !src/**/build/
6 |
7 | # Ignore Gradle GUI config
8 | gradle-app.setting
9 |
10 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
11 | !gradle-wrapper.jar
12 |
13 | # Cache of project
14 | .gradletasknamecache
15 |
16 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
17 | # gradle/wrapper/gradle-wrapper.properties
18 |
19 | ### Kotlin template
20 | # Compiled class file
21 | *.class
22 |
23 | # Log file
24 | *.log
25 |
26 | # BlueJ files
27 | *.ctxt
28 |
29 | # Mobile Tools for Java (J2ME)
30 | .mtj.tmp/
31 |
32 | # Package Files #
33 | *.jar
34 | *.war
35 | *.nar
36 | *.ear
37 | *.zip
38 | *.tar.gz
39 | *.rar
40 |
41 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
42 | hs_err_pid*
43 |
44 | ### Android template
45 | # Built application files
46 | *.apk
47 | *.aar
48 | *.ap_
49 | *.aab
50 |
51 | # Files for the ART/Dalvik VM
52 | *.dex
53 |
54 | # Java class files
55 | *.class
56 |
57 | # Generated files
58 | bin/
59 | gen/
60 | out/
61 | # Uncomment the following line in case you need and you don't have the release build type files in your app
62 | # release/
63 |
64 | # Gradle files
65 | .gradle/
66 | build/
67 |
68 | # Local configuration file (sdk path, etc)
69 | local.properties
70 |
71 | # Proguard folder generated by Eclipse
72 | proguard/
73 |
74 | # Log Files
75 | *.log
76 |
77 | # Android Studio Navigation editor temp files
78 | .navigation/
79 |
80 | # Android Studio captures folder
81 | captures/
82 |
83 | # IntelliJ
84 | *.iml
85 | .idea/workspace.xml
86 | .idea/tasks.xml
87 | .idea/gradle.xml
88 | .idea/assetWizardSettings.xml
89 | .idea/dictionaries
90 | .idea/libraries
91 | # Android Studio 3 in .gitignore file.
92 | .idea/caches
93 | .idea/modules.xml
94 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
95 | .idea/navEditor.xml
96 |
97 | # Keystore files
98 | # Uncomment the following lines if you do not want to check your keystore files in.
99 | #*.jks
100 | #*.keystore
101 |
102 | # External native build folder generated in Android Studio 2.2 and later
103 | .externalNativeBuild
104 | .cxx/
105 |
106 | # Google Services (e.g. APIs or Firebase)
107 | # google-services.json
108 |
109 | # Freeline
110 | freeline.py
111 | freeline/
112 | freeline_project_description.json
113 |
114 | # fastlane
115 | fastlane/report.xml
116 | fastlane/Preview.html
117 | fastlane/screenshots
118 | fastlane/test_output
119 | fastlane/readme.md
120 |
121 | # Version control
122 | vcs.xml
123 |
124 | # lint
125 | lint/intermediates/
126 | lint/generated/
127 | lint/outputs/
128 | lint/tmp/
129 | # lint/reports/
130 |
131 | # Android Profiling
132 | *.hprof
133 |
134 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/fourteen/Decoder.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.fourteen
2 |
3 | interface Decoder {
4 | val a: Int
5 | val b: Int
6 | val c: Int
7 | val d: Int
8 | val e: Int
9 | val f: Int
10 | val g1: Int
11 | val g2: Int
12 | val h: Int
13 | val i: Int
14 | val j: Int
15 | val k: Int
16 | val l: Int
17 | val m: Int
18 | }
19 |
20 | class BinaryDecoder(data: Int = 0) : Decoder {
21 | override val a = data and 0b0000000000000001
22 | override val b = data and 0b0000000000000010
23 | override val c = data and 0b0000000000000100
24 | override val d = data and 0b0000000000001000
25 | override val e = data and 0b0000000000010000
26 | override val f = data and 0b0000000000100000
27 | override val g1 = data and 0b0000000001000000
28 | override val g2 = data and 0b0000000010000000
29 | override val h = data and 0b0000000100000000
30 | override val i = data and 0b0000001000000000
31 | override val j = data and 0b0000010000000000
32 | override val k = data and 0b0000100000000000
33 | override val l = data and 0b0001000000000000
34 | override val m = data and 0b0010000000000000
35 |
36 | companion object {
37 | fun mapToDisplay(number: Int): Int {
38 | return when (number) {
39 | 0 -> 0xC3F
40 | 1 -> 0x406
41 | 2 -> 0xDB
42 | 3 -> 0x8F
43 | 4 -> 0xE6
44 | 5 -> 0xED
45 | 6 -> 0xFD
46 | 7 -> 0x1401
47 | 8 -> 0xFF
48 | 9 -> 0xE7
49 | else -> 0x0
50 | }
51 | }
52 | fun mapToDisplay(char: Char): Int {
53 | return when (char) {
54 | 'A' -> 0xF7
55 | 'B' -> 0x128F
56 | 'C' -> 0x39
57 | 'D' -> 0x120F
58 | 'E' -> 0xF9
59 | 'F' -> 0xF1
60 | 'G' -> 0xBD
61 | 'H' -> 0xF6
62 | 'I' -> 0x1209
63 | 'J' -> 0x1E
64 | 'K' -> 0x2470
65 | 'L' -> 0x38
66 | 'M' -> 0x536
67 | 'N' -> 0x2136
68 | 'O' -> 0x3F
69 | 'P' -> 0xF3
70 | 'Q' -> 0x203F
71 | 'R' -> 0x20F3
72 | 'S' -> 0x18D
73 | 'T' -> 0x1201
74 | 'U' -> 0x3E
75 | 'V' -> 0xC30
76 | 'W' -> 0x2836
77 | 'X' -> 0x2D00
78 | 'Y' -> 0x1500
79 | 'Z' -> 0xC09
80 | '0' -> mapToDisplay(0)
81 | '1' -> mapToDisplay(1)
82 | '2' -> mapToDisplay(2)
83 | '3' -> mapToDisplay(3)
84 | '4' -> mapToDisplay(4)
85 | '5' -> mapToDisplay(5)
86 | '6' -> mapToDisplay(6)
87 | '7' -> mapToDisplay(7)
88 | '8' -> mapToDisplay(8)
89 | '9' -> mapToDisplay(9)
90 | else -> 0x0
91 | }
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/seven/Animations.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.seven
2 |
3 | import kotlinx.coroutines.currentCoroutineContext
4 | import kotlinx.coroutines.delay
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flow
7 | import kotlinx.coroutines.isActive
8 | import kotlin.time.Duration
9 | import kotlin.time.ExperimentalTime
10 |
11 | class Animations {
12 | companion object {
13 |
14 | @ExperimentalTime
15 | private fun animationFlowGenerator(frames: List): Flow {
16 | return flow {
17 | while (currentCoroutineContext().isActive) {
18 | frames.forEach {
19 | emit(it)
20 | delay(Duration.milliseconds(100))
21 | }
22 | }
23 | }
24 | }
25 |
26 | @ExperimentalTime
27 | val roundOutsideSingleSeg = animationFlowGenerator(
28 | listOf(
29 | 0b000000001,
30 | 0b000000010,
31 | 0b000000100,
32 | 0b000001000,
33 | 0b000010000,
34 | 0b000100000
35 | )
36 | )
37 |
38 | @ExperimentalTime
39 | val roundOutsideDoubleSeg = animationFlowGenerator(
40 | listOf(
41 | 0b000000001,
42 | 0b000000011,
43 | 0b000000010,
44 | 0b000000110,
45 | 0b000000100,
46 | 0b000001100,
47 | 0b000001000,
48 | 0b000011000,
49 | 0b000010000,
50 | 0b000110000,
51 | 0b000100000,
52 | 0b000100001
53 | )
54 | )
55 |
56 | @ExperimentalTime
57 | val fillOutside = animationFlowGenerator(
58 | listOf(
59 | 0b000000000,
60 | 0b000000001,
61 | 0b000000011,
62 | 0b000000111,
63 | 0b000001111,
64 | 0b000011111,
65 | 0b000111111
66 | )
67 | )
68 |
69 | @ExperimentalTime
70 | val pathEight = animationFlowGenerator(
71 | listOf(
72 | 0b000000011,
73 | 0b001000010,
74 | 0b001010000,
75 | 0b000011000,
76 | 0b000001100,
77 | 0b001000100,
78 | 0b001100000,
79 | 0b000100001
80 | )
81 | )
82 |
83 | @ExperimentalTime
84 | val opposing = animationFlowGenerator(
85 | listOf(
86 | 0b000001001,
87 | 0b000010010,
88 | 0b000100100,
89 | )
90 | )
91 |
92 | @ExperimentalTime
93 | val fallSingle = animationFlowGenerator(
94 | listOf(
95 | 0b000000001,
96 | 0b000100010,
97 | 0b001000000,
98 | 0b000010100,
99 | 0b000001000,
100 | )
101 | )
102 |
103 | @ExperimentalTime
104 | val fallFill = animationFlowGenerator(
105 | listOf(
106 | 0b000000000,
107 | 0b000000001,
108 | 0b000100011,
109 | 0b001100011,
110 | 0b001110111,
111 | 0b001111111,
112 | 0b001111110,
113 | 0b001011100,
114 | 0b000011100,
115 | 0b000001000,
116 | )
117 | )
118 |
119 | @ExperimentalTime
120 | val rightToLeftFill = animationFlowGenerator(
121 | listOf(
122 | 0b000000110,
123 | 0b001001111,
124 | 0b001111111,
125 | 0b001111001,
126 | 0b000110000,
127 | 0b000000000,
128 | )
129 | )
130 | }
131 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rabross/segmenteddisplay/app/SevenSegmentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.app
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material.Surface
8 | import androidx.compose.runtime.mutableStateOf
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.unit.dp
13 | import androidx.lifecycle.lifecycleScope
14 | import com.rabross.segmenteddisplay.SingleColorLed
15 | import com.rabross.segmenteddisplay.seven.Animations
16 | import com.rabross.segmenteddisplay.seven.BinaryDecoder
17 | import com.rabross.segmenteddisplay.seven.DigitalClock
18 | import com.rabross.segmenteddisplay.seven.SegmentDisplay
19 | import kotlinx.coroutines.currentCoroutineContext
20 | import kotlinx.coroutines.delay
21 | import kotlinx.coroutines.flow.*
22 | import kotlinx.coroutines.isActive
23 | import java.util.*
24 | import kotlin.time.Duration
25 | import kotlin.time.ExperimentalTime
26 |
27 | @OptIn(ExperimentalTime::class)
28 | class SevenSegmentActivity : ComponentActivity() {
29 |
30 | private val redLed = SingleColorLed(Color.Red, Color.Black)
31 | private val greenLed = SingleColorLed(Color.Green, Color.Black)
32 | private val blueLed = SingleColorLed(Color.Blue, Color.Black)
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | setContent {
37 | val animationRightToLeftFill = remember { mutableStateOf(0) }
38 | val animationRoundOutsideDoubleSeg = remember { mutableStateOf(0) }
39 | val animationFallFill = remember { mutableStateOf(0) }
40 |
41 | val hourFirst = remember { mutableStateOf(0) }
42 | val hourSecond = remember { mutableStateOf(0) }
43 | val minuteFirst = remember { mutableStateOf(0) }
44 | val minuteSecond = remember { mutableStateOf(0) }
45 | val secondFirst = remember { mutableStateOf(0) }
46 | val secondSecond = remember { mutableStateOf(0) }
47 |
48 | Surface(color = Color.Black) {
49 | Column(modifier = Modifier.padding(24.dp)) {
50 | DigitalClock(
51 | Modifier.weight(1f),
52 | BinaryDecoder.mapToDisplay(hourFirst.value),
53 | BinaryDecoder.mapToDisplay(hourSecond.value),
54 | BinaryDecoder.mapToDisplay(minuteFirst.value),
55 | BinaryDecoder.mapToDisplay(minuteSecond.value),
56 | BinaryDecoder.mapToDisplay(secondFirst.value),
57 | BinaryDecoder.mapToDisplay(secondSecond.value),
58 | delimiterSignal = 0b11
59 | )
60 | Row(
61 | Modifier
62 | .weight(1f)
63 | .fillMaxWidth()
64 | ) {
65 | SegmentDisplay(
66 | modifier = Modifier.weight(1f).padding(4.dp),
67 | decoder = BinaryDecoder(animationFallFill.value),
68 | led = redLed
69 | )
70 | SegmentDisplay(
71 | modifier = Modifier.weight(1f).padding(4.dp),
72 | decoder = BinaryDecoder(animationRightToLeftFill.value),
73 | led = greenLed
74 | )
75 | SegmentDisplay(
76 | modifier = Modifier.weight(1f).padding(4.dp),
77 | decoder = BinaryDecoder(animationRoundOutsideDoubleSeg.value),
78 | led = blueLed
79 | )
80 | }
81 | }
82 | }
83 |
84 | Animations.rightToLeftFill
85 | .onEach { animationRightToLeftFill.value = it }
86 | .launchIn(lifecycleScope)
87 |
88 | Animations.roundOutsideDoubleSeg
89 | .onEach { animationRoundOutsideDoubleSeg.value = it }
90 | .launchIn(lifecycleScope)
91 |
92 | Animations.fallFill
93 | .onEach { animationFallFill.value = it }
94 | .launchIn(lifecycleScope)
95 |
96 | tickerFlow(Duration.seconds(1))
97 | .map { Calendar.getInstance() }
98 | .distinctUntilChanged { old, new ->
99 | old.get(Calendar.SECOND) == new.get(Calendar.SECOND)
100 | }
101 | .onEach { calendar ->
102 | val hour = calendar.get(Calendar.HOUR_OF_DAY)
103 | val hourDigits = hour.splitDigits()
104 | hourFirst.value = hourDigits.first
105 | hourSecond.value = hourDigits.second
106 |
107 | val minute = calendar.get(Calendar.MINUTE)
108 | val minuteDigits = minute.splitDigits()
109 | minuteFirst.value = minuteDigits.first
110 | minuteSecond.value = minuteDigits.second
111 |
112 | val second = calendar.get(Calendar.SECOND)
113 | val secondDigits = second.splitDigits()
114 | secondFirst.value = secondDigits.first
115 | secondSecond.value = secondDigits.second
116 | }
117 | .launchIn(lifecycleScope)
118 | }
119 | }
120 |
121 | private fun tickerFlow(period: Duration) = flow {
122 | while (currentCoroutineContext().isActive) {
123 | emit(Unit)
124 | delay(period)
125 | }
126 | }
127 |
128 | private fun Int.splitDigits(): Pair {
129 | return when {
130 | this <= 0 -> 0 to 0
131 | else -> {
132 | val secondDigit: Int = this % 10
133 | val firstDigit: Int = if (this < 10) 0 else this / 10 % 10
134 | firstDigit to secondDigit
135 | }
136 | }
137 | }
138 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.aar
6 | *.ap_
7 | *.aab
8 |
9 | # Files for the ART/Dalvik VM
10 | *.dex
11 |
12 | # Java class files
13 | *.class
14 |
15 | # Generated files
16 | bin/
17 | gen/
18 | out/
19 | # Uncomment the following line in case you need and you don't have the release build type files in your app
20 | # release/
21 |
22 | # Gradle files
23 | .gradle/
24 | build/
25 |
26 | # Local configuration file (sdk path, etc)
27 | local.properties
28 |
29 | # Proguard folder generated by Eclipse
30 | proguard/
31 |
32 | # Log Files
33 | *.log
34 |
35 | # Android Studio Navigation editor temp files
36 | .navigation/
37 |
38 | # Android Studio captures folder
39 | captures/
40 |
41 | # IntelliJ
42 | *.iml
43 | .idea/workspace.xml
44 | .idea/tasks.xml
45 | .idea/gradle.xml
46 | .idea/assetWizardSettings.xml
47 | .idea/dictionaries
48 | .idea/libraries
49 | # Android Studio 3 in .gitignore file.
50 | .idea/caches
51 | .idea/modules.xml
52 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
53 | .idea/navEditor.xml
54 |
55 | # Keystore files
56 | # Uncomment the following lines if you do not want to check your keystore files in.
57 | #*.jks
58 | #*.keystore
59 |
60 | # External native build folder generated in Android Studio 2.2 and later
61 | .externalNativeBuild
62 | .cxx/
63 |
64 | # Google Services (e.g. APIs or Firebase)
65 | # google-services.json
66 |
67 | # Freeline
68 | freeline.py
69 | freeline/
70 | freeline_project_description.json
71 |
72 | # fastlane
73 | fastlane/report.xml
74 | fastlane/Preview.html
75 | fastlane/screenshots
76 | fastlane/test_output
77 | fastlane/readme.md
78 |
79 | # Version control
80 | vcs.xml
81 |
82 | # lint
83 | lint/intermediates/
84 | lint/generated/
85 | lint/outputs/
86 | lint/tmp/
87 | # lint/reports/
88 |
89 | # Android Profiling
90 | *.hprof
91 |
92 | ### Kotlin template
93 | # Compiled class file
94 | *.class
95 |
96 | # Log file
97 | *.log
98 |
99 | # BlueJ files
100 | *.ctxt
101 |
102 | # Mobile Tools for Java (J2ME)
103 | .mtj.tmp/
104 |
105 | # Package Files #
106 | *.jar
107 | *.war
108 | *.nar
109 | *.ear
110 | *.zip
111 | *.tar.gz
112 | *.rar
113 |
114 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
115 | hs_err_pid*
116 |
117 | ### Kotlin template
118 | # Compiled class file
119 | *.class
120 |
121 | # Log file
122 | *.log
123 |
124 | # BlueJ files
125 | *.ctxt
126 |
127 | # Mobile Tools for Java (J2ME)
128 | .mtj.tmp/
129 |
130 | # Package Files #
131 | *.jar
132 | *.war
133 | *.nar
134 | *.ear
135 | *.zip
136 | *.tar.gz
137 | *.rar
138 |
139 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
140 | hs_err_pid*
141 |
142 | ### Gradle template
143 | .gradle
144 | **/build/
145 | !src/**/build/
146 |
147 | # Ignore Gradle GUI config
148 | gradle-app.setting
149 |
150 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
151 | !gradle-wrapper.jar
152 |
153 | # Cache of project
154 | .gradletasknamecache
155 |
156 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
157 | # gradle/wrapper/gradle-wrapper.properties
158 |
159 | ### Android template
160 | # Built application files
161 | *.apk
162 | *.aar
163 | *.ap_
164 | *.aab
165 |
166 | # Files for the ART/Dalvik VM
167 | *.dex
168 |
169 | # Java class files
170 | *.class
171 |
172 | # Generated files
173 | bin/
174 | gen/
175 | out/
176 | # Uncomment the following line in case you need and you don't have the release build type files in your app
177 | # release/
178 |
179 | # Gradle files
180 | .gradle/
181 | build/
182 |
183 | # Local configuration file (sdk path, etc)
184 | local.properties
185 |
186 | # Proguard folder generated by Eclipse
187 | proguard/
188 |
189 | # Log Files
190 | *.log
191 |
192 | # Android Studio Navigation editor temp files
193 | .navigation/
194 |
195 | # Android Studio captures folder
196 | captures/
197 |
198 | # IntelliJ
199 | *.iml
200 | .idea/workspace.xml
201 | .idea/tasks.xml
202 | .idea/gradle.xml
203 | .idea/assetWizardSettings.xml
204 | .idea/dictionaries
205 | .idea/libraries
206 | # Android Studio 3 in .gitignore file.
207 | .idea/caches
208 | .idea/modules.xml
209 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
210 | .idea/navEditor.xml
211 |
212 | # Keystore files
213 | # Uncomment the following lines if you do not want to check your keystore files in.
214 | #*.jks
215 | #*.keystore
216 |
217 | # External native build folder generated in Android Studio 2.2 and later
218 | .externalNativeBuild
219 | .cxx/
220 |
221 | # Google Services (e.g. APIs or Firebase)
222 | # google-services.json
223 |
224 | # Freeline
225 | freeline.py
226 | freeline/
227 | freeline_project_description.json
228 |
229 | # fastlane
230 | fastlane/report.xml
231 | fastlane/Preview.html
232 | fastlane/screenshots
233 | fastlane/test_output
234 | fastlane/readme.md
235 |
236 | # Version control
237 | vcs.xml
238 |
239 | # lint
240 | lint/intermediates/
241 | lint/generated/
242 | lint/outputs/
243 | lint/tmp/
244 | # lint/reports/
245 |
246 | # Android Profiling
247 | *.hprof
248 |
249 | ### Android template
250 | # Built application files
251 | *.apk
252 | *.aar
253 | *.ap_
254 | *.aab
255 |
256 | # Files for the ART/Dalvik VM
257 | *.dex
258 |
259 | # Java class files
260 | *.class
261 |
262 | # Generated files
263 | bin/
264 | gen/
265 | out/
266 | # Uncomment the following line in case you need and you don't have the release build type files in your app
267 | # release/
268 |
269 | # Gradle files
270 | .gradle/
271 | build/
272 |
273 | # Local configuration file (sdk path, etc)
274 | local.properties
275 |
276 | # Proguard folder generated by Eclipse
277 | proguard/
278 |
279 | # Log Files
280 | *.log
281 |
282 | # Android Studio Navigation editor temp files
283 | .navigation/
284 |
285 | # Android Studio captures folder
286 | captures/
287 |
288 | # IntelliJ
289 | *.iml
290 | .idea/workspace.xml
291 | .idea/tasks.xml
292 | .idea/gradle.xml
293 | .idea/assetWizardSettings.xml
294 | .idea/dictionaries
295 | .idea/libraries
296 | # Android Studio 3 in .gitignore file.
297 | .idea/caches
298 | .idea/modules.xml
299 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
300 | .idea/navEditor.xml
301 |
302 | # Keystore files
303 | # Uncomment the following lines if you do not want to check your keystore files in.
304 | #*.jks
305 | #*.keystore
306 |
307 | # External native build folder generated in Android Studio 2.2 and later
308 | .externalNativeBuild
309 | .cxx/
310 |
311 | # Google Services (e.g. APIs or Firebase)
312 | # google-services.json
313 |
314 | # Freeline
315 | freeline.py
316 | freeline/
317 | freeline_project_description.json
318 |
319 | # fastlane
320 | fastlane/report.xml
321 | fastlane/Preview.html
322 | fastlane/screenshots
323 | fastlane/test_output
324 | fastlane/readme.md
325 |
326 | # Version control
327 | vcs.xml
328 |
329 | # lint
330 | lint/intermediates/
331 | lint/generated/
332 | lint/outputs/
333 | lint/tmp/
334 | # lint/reports/
335 |
336 | # Android Profiling
337 | *.hprof
338 |
339 | ### Gradle template
340 | .gradle
341 | **/build/
342 | !src/**/build/
343 |
344 | # Ignore Gradle GUI config
345 | gradle-app.setting
346 |
347 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
348 | !gradle-wrapper.jar
349 |
350 | # Cache of project
351 | .gradletasknamecache
352 |
353 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
354 | # gradle/wrapper/gradle-wrapper.properties
355 |
356 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rabross/segmenteddisplay/app/FourteenSegmentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.app
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.material.Surface
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.unit.dp
15 | import androidx.lifecycle.lifecycleScope
16 | import com.rabross.segmenteddisplay.fourteen.BinaryDecoder
17 | import com.rabross.segmenteddisplay.fourteen.DigitalClock
18 | import com.rabross.segmenteddisplay.fourteen.SegmentDisplay
19 | import kotlinx.coroutines.currentCoroutineContext
20 | import kotlinx.coroutines.delay
21 | import kotlinx.coroutines.flow.*
22 | import kotlinx.coroutines.isActive
23 | import java.util.*
24 | import kotlin.time.Duration
25 | import kotlin.time.ExperimentalTime
26 |
27 | @OptIn(ExperimentalTime::class)
28 | class FourteenSegmentActivity : ComponentActivity() {
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 | setContent {
33 | val hourFirst = remember { mutableStateOf(0) }
34 | val hourSecond = remember { mutableStateOf(0) }
35 | val minuteFirst = remember { mutableStateOf(0) }
36 | val minuteSecond = remember { mutableStateOf(0) }
37 | val secondFirst = remember { mutableStateOf(0) }
38 | val secondSecond = remember { mutableStateOf(0) }
39 |
40 | Surface(color = Color.Black) {
41 | Column(modifier = Modifier.padding(24.dp)) {
42 | DigitalClock(
43 | Modifier.weight(1f),
44 | BinaryDecoder.mapToDisplay(hourFirst.value),
45 | BinaryDecoder.mapToDisplay(hourSecond.value),
46 | BinaryDecoder.mapToDisplay(minuteFirst.value),
47 | BinaryDecoder.mapToDisplay(minuteSecond.value),
48 | BinaryDecoder.mapToDisplay(secondFirst.value),
49 | BinaryDecoder.mapToDisplay(secondSecond.value),
50 | 3
51 | )
52 | Row(Modifier.weight(1f)) {
53 | SegmentDisplay(
54 | modifier = Modifier
55 | .weight(1f)
56 | .padding(4.dp),
57 | decoder = BinaryDecoder(
58 | BinaryDecoder.mapToDisplay('H')
59 | )
60 | )
61 | SegmentDisplay(
62 | modifier = Modifier
63 | .weight(1f)
64 | .padding(4.dp),
65 | decoder = BinaryDecoder(
66 | BinaryDecoder.mapToDisplay('E')
67 | )
68 | )
69 | SegmentDisplay(
70 | modifier = Modifier
71 | .weight(1f)
72 | .padding(4.dp),
73 | decoder = BinaryDecoder(
74 | BinaryDecoder.mapToDisplay('L')
75 | )
76 | )
77 | SegmentDisplay(
78 | modifier = Modifier
79 | .weight(1f)
80 | .padding(4.dp),
81 | decoder = BinaryDecoder(
82 | BinaryDecoder.mapToDisplay('L')
83 | )
84 | )
85 | SegmentDisplay(
86 | modifier = Modifier
87 | .weight(1f)
88 | .padding(4.dp),
89 | decoder = BinaryDecoder(
90 | BinaryDecoder.mapToDisplay('O')
91 | )
92 | )
93 | }
94 | Row(Modifier.weight(1f)) {
95 | SegmentDisplay(
96 | modifier = Modifier
97 | .weight(1f)
98 | .padding(4.dp),
99 | decoder = BinaryDecoder(
100 | BinaryDecoder.mapToDisplay('W')
101 | )
102 | )
103 | SegmentDisplay(
104 | modifier = Modifier
105 | .weight(1f)
106 | .padding(4.dp),
107 | decoder = BinaryDecoder(
108 | BinaryDecoder.mapToDisplay('O')
109 | )
110 | )
111 | SegmentDisplay(
112 | modifier = Modifier
113 | .weight(1f)
114 | .padding(4.dp),
115 | decoder = BinaryDecoder(
116 | BinaryDecoder.mapToDisplay('R')
117 | )
118 | )
119 | SegmentDisplay(
120 | modifier = Modifier
121 | .weight(1f)
122 | .padding(4.dp),
123 | decoder = BinaryDecoder(
124 | BinaryDecoder.mapToDisplay('L')
125 | )
126 | )
127 | SegmentDisplay(
128 | modifier = Modifier
129 | .weight(1f)
130 | .padding(4.dp),
131 | decoder = BinaryDecoder(
132 | BinaryDecoder.mapToDisplay('D')
133 | )
134 | )
135 | }
136 | }
137 | }
138 |
139 | tickerFlow(Duration.seconds(1))
140 | .map { Calendar.getInstance() }
141 | .distinctUntilChanged { old, new ->
142 | old.get(Calendar.SECOND) == new.get(Calendar.SECOND)
143 | }
144 | .onEach { calendar ->
145 | val hour = calendar.get(Calendar.HOUR_OF_DAY)
146 | val hourDigits = hour.splitDigits()
147 | hourFirst.value = hourDigits.first
148 | hourSecond.value = hourDigits.second
149 |
150 | val minute = calendar.get(Calendar.MINUTE)
151 | val minuteDigits = minute.splitDigits()
152 | minuteFirst.value = minuteDigits.first
153 | minuteSecond.value = minuteDigits.second
154 |
155 | val second = calendar.get(Calendar.SECOND)
156 | val secondDigits = second.splitDigits()
157 | secondFirst.value = secondDigits.first
158 | secondSecond.value = secondDigits.second
159 | }
160 | .launchIn(lifecycleScope)
161 | }
162 | }
163 |
164 | private fun tickerFlow(period: Duration) = flow {
165 | while (currentCoroutineContext().isActive) {
166 | emit(Unit)
167 | delay(period)
168 | }
169 | }
170 |
171 | private fun Int.splitDigits(): Pair {
172 | return when {
173 | this <= 0 -> 0 to 0
174 | else -> {
175 | val secondDigit: Int = this % 10
176 | val firstDigit: Int = if (this < 10) 0 else this / 10 % 10
177 | firstDigit to secondDigit
178 | }
179 | }
180 | }
181 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rabross/segmenteddisplay/app/SevenSegmentScreenActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.app
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.BitmapFactory
5 | import android.graphics.Canvas
6 | import android.os.Bundle
7 | import androidx.activity.ComponentActivity
8 | import androidx.activity.compose.setContent
9 | import androidx.compose.foundation.Image
10 | import androidx.compose.foundation.background
11 | import androidx.compose.foundation.layout.*
12 | import androidx.compose.material.Surface
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.res.painterResource
15 | import androidx.compose.ui.unit.dp
16 | import androidx.core.graphics.scale
17 | import com.rabross.segmenteddisplay.seven.Decoder
18 | import com.rabross.segmenteddisplay.seven.SegmentDisplay
19 | import java.nio.ByteBuffer
20 | import java.security.InvalidParameterException
21 | import androidx.compose.ui.graphics.Color as ComposeColor
22 |
23 | class SevenSegmentScreenActivity : ComponentActivity() {
24 |
25 | private val columns = 25
26 | private val rows = 10
27 | private val bufferWidth = columns * 2
28 | private val bufferHeight = rows * 5
29 | private val imageResource = R.drawable.color_bitmap
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | val bitmap = Bitmap.createBitmap(bufferWidth, bufferHeight, Bitmap.Config.ARGB_8888)
34 | .also { Canvas(it).drawBitmap(
35 | BitmapFactory.decodeResource(resources, imageResource)
36 | .scale(bufferWidth, bufferHeight), 0f, 0f, null
37 | )
38 | }
39 |
40 | val screenBuffer = ByteBuffer.allocate(bitmap.allocationByteCount)
41 | .also { bitmap.copyPixelsToBuffer(it) }
42 |
43 | setContent {
44 | Surface(modifier = Modifier.fillMaxSize(), color = ComposeColor.Black) {
45 | Column(modifier = Modifier.padding(24.dp)) {
46 | Image(
47 | modifier = Modifier
48 | .background(color = ComposeColor.White)
49 | .fillMaxWidth(),
50 | painter = painterResource(id = imageResource),
51 | contentDescription = "preview")
52 | Spacer(modifier = Modifier.height(24.dp))
53 | for (row in 0 until rows) {
54 | Row {
55 | for (column in 0 until columns) {
56 | val sevenSegmentIndex = (row * columns) + column
57 | SegmentDisplay(
58 | modifier = Modifier
59 | .weight(1f)
60 | .padding(2.dp),
61 | decoder = ARGB8888BufferDecoder(screenBuffer, bufferWidth, bufferHeight, columns, rows, sevenSegmentIndex),
62 | led = { value -> ComposeColor(value) }
63 | )
64 | }
65 | }
66 | }
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
73 | class Alpha8BufferDecoder(
74 | imageBuffer: ByteBuffer,
75 | bufferWidth: Int,
76 | bufferHeight: Int,
77 | sevenSegmentCountX: Int,
78 | sevenSegmentCountY: Int,
79 | sevenSegmentIndex: Int
80 | ) : Decoder {
81 | override val a = imageBuffer.get(0.toALPHA8BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toInt()
82 | override val b = imageBuffer.get(1.toALPHA8BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toInt()
83 | override val c = imageBuffer.get(2.toALPHA8BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toInt()
84 | override val d = imageBuffer.get(3.toALPHA8BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toInt()
85 | override val e = imageBuffer.get(4.toALPHA8BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toInt()
86 | override val f = imageBuffer.get(5.toALPHA8BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toInt()
87 | override val g = imageBuffer.get(6.toALPHA8BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toInt()
88 | }
89 |
90 | class ARGB8888BufferDecoder(
91 | imageBuffer: ByteBuffer,
92 | bufferWidth: Int,
93 | bufferHeight: Int,
94 | sevenSegmentCountX: Int,
95 | sevenSegmentCountY: Int,
96 | sevenSegmentIndex: Int
97 | ) : Decoder {
98 | override val a = imageBuffer.getInt(0.toARGB8888BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toARGB()
99 | override val b = imageBuffer.getInt(1.toARGB8888BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toARGB()
100 | override val c = imageBuffer.getInt(2.toARGB8888BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toARGB()
101 | override val d = imageBuffer.getInt(3.toARGB8888BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toARGB()
102 | override val e = imageBuffer.getInt(4.toARGB8888BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toARGB()
103 | override val f = imageBuffer.getInt(5.toARGB8888BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toARGB()
104 | override val g = imageBuffer.getInt(6.toARGB8888BufferIndex(bufferWidth, bufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex)).toARGB()
105 |
106 | //Pixel data read from the image buffer us stored at RGBA and must be converted to ARGB
107 | private fun Int.toARGB(): Int {
108 | return (this shl 24) or (this shr 8)
109 | }
110 | }
111 |
112 | /*
113 | Each segment is 2x5 pixels
114 | 0,0 = A
115 | 0,1 = F
116 | 1,1 = B
117 | 0,2 = G
118 | 0,3 = E
119 | 1,3 = C
120 | 0,4 = D
121 |
122 | Segment index:
123 | A = 0
124 | B = 1
125 | C = 2
126 | D = 3
127 | E = 4
128 | F = 5
129 | G = 6
130 | */
131 |
132 | internal fun Int.toALPHA8BufferIndex(
133 | imageBufferWidth: Int,
134 | imageBufferHeight: Int,
135 | sevenSegmentCountX: Int,
136 | sevenSegmentCountY: Int,
137 | sevenSegmentIndex: Int
138 | ): Int {
139 | val segmentWidth = 2
140 | val segmentHeight = 5
141 |
142 | if(imageBufferWidth % segmentWidth > 0 || imageBufferHeight % segmentHeight > 0) throw InvalidParameterException("Image buffer must be divisible by 7-segment size 2x5")
143 | if(imageBufferWidth < sevenSegmentCountX * segmentWidth) throw InvalidParameterException("Image buffer width must be large enough to support given 7-segment grid width")
144 | if(imageBufferHeight < sevenSegmentCountY * segmentHeight) throw InvalidParameterException("Image buffer height must be large enough to support given 7-segment grid height")
145 |
146 | val segmentOffsetX = sevenSegmentIndex % sevenSegmentCountX
147 | val segmentOffsetY = sevenSegmentIndex / sevenSegmentCountX
148 |
149 | val imageOffsetX = segmentOffsetX * segmentWidth
150 | val imageOffsetY = segmentOffsetY * segmentHeight
151 |
152 | val imageCoordinate = when(this) {
153 | /*A*/ 0 -> imageOffsetX to imageOffsetY
154 | /*B*/ 1 -> imageOffsetX + 1 to imageOffsetY + 1
155 | /*C*/ 2 -> imageOffsetX + 1 to imageOffsetY + 3
156 | /*D*/ 3 -> imageOffsetX to imageOffsetY + 4
157 | /*E*/ 4 -> imageOffsetX to imageOffsetY + 3
158 | /*F*/ 5 -> imageOffsetX to imageOffsetY + 1
159 | /*G*/ 6 -> imageOffsetX to imageOffsetY + 2
160 | else -> 0 to 0
161 | }
162 |
163 | return imageCoordinate.second * imageBufferWidth + imageCoordinate.first
164 | }
165 |
166 | internal fun Int.toARGB8888BufferIndex(
167 | imageBufferWidth: Int,
168 | imageBufferHeight: Int,
169 | sevenSegmentCountX: Int,
170 | sevenSegmentCountY: Int,
171 | sevenSegmentIndex: Int
172 | ): Int {
173 | return toALPHA8BufferIndex(imageBufferWidth, imageBufferHeight, sevenSegmentCountX, sevenSegmentCountY, sevenSegmentIndex) * 4 //bytes
174 | }
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/seven/Composable.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.seven
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.aspectRatio
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.material.Surface
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.geometry.Offset
12 | import androidx.compose.ui.geometry.Size
13 | import androidx.compose.ui.graphics.Brush
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.graphics.Path
16 | import androidx.compose.ui.graphics.drawscope.DrawScope
17 | import androidx.compose.ui.tooling.preview.Preview
18 | import androidx.compose.ui.unit.dp
19 | import com.rabross.segmenteddisplay.Led
20 | import com.rabross.segmenteddisplay.SingleColorLed
21 | import com.rabross.segmenteddisplay.delimiter.Delimiter
22 | import com.rabross.segmenteddisplay.delimiter.BinaryDecoder as DelimiterBinaryDecoder
23 |
24 | @Preview
25 | @Composable
26 | fun FlatSegmentDisplayPreview() {
27 | Surface(color = Color.Black) {
28 | SegmentDisplay(
29 | decoder = BinaryDecoder(BinaryDecoder.mapToDisplay(5)),
30 | segmentStyle = SegmentStyle.FLAT
31 | )
32 | }
33 | }
34 |
35 | @Preview
36 | @Composable
37 | fun DiffuserSegmentDisplayPreview() {
38 | Surface(color = Color.Black) {
39 | SegmentDisplay(
40 | decoder = BinaryDecoder(BinaryDecoder.mapToDisplay(5)),
41 | segmentStyle = SegmentStyle.DIFFUSER
42 | )
43 | }
44 | }
45 |
46 | @Preview
47 | @Composable
48 | fun DigitalClockPreview() {
49 | Surface(color = Color.Black) {
50 | DigitalClock()
51 | }
52 | }
53 |
54 | @Composable
55 | fun SegmentDisplay(
56 | modifier: Modifier = Modifier,
57 | segmentScale: Int = 3,
58 | spacingRatio: Float = 0.2f,
59 | led: Led = defaultLed,
60 | segmentStyle: SegmentStyle = SegmentStyle.FLAT,
61 | decoder: Decoder = BinaryDecoder()
62 | ) {
63 | Canvas(modifier = modifier
64 | .aspectRatio((1f + segmentScale + 1f) / (1f + segmentScale + 1f + segmentScale + 1f), true)
65 | .size(100.dp)) {
66 |
67 | val scaleWidth = 1 + segmentScale + 1
68 | val scaleHeight = 1 + segmentScale + 1 + segmentScale + 1
69 |
70 | val segmentWidthByWidth = size.width / scaleWidth
71 | val segmentWidthByHeight = size.height / scaleHeight
72 |
73 | val segmentWidth = if (scaleHeight * segmentWidthByWidth < size.height) segmentWidthByWidth else segmentWidthByHeight
74 | val segmentLength = segmentScale * segmentWidth
75 |
76 | val horizontalSegmentSize = Size(segmentLength, segmentWidth)
77 | val verticalSegmentSize = Size(segmentWidth, segmentLength)
78 |
79 | val aOffset = Offset(segmentWidth, 0f)
80 | val bOffset = Offset(segmentLength + segmentWidth, segmentWidth)
81 | val cOffset = Offset(segmentLength + segmentWidth, segmentLength + segmentWidth + segmentWidth)
82 | val dOffset = Offset(segmentWidth, segmentLength + segmentWidth + segmentLength + segmentWidth)
83 | val eOffset = Offset(0f, segmentLength + segmentWidth + segmentWidth)
84 | val fOffset = Offset(0f, segmentWidth)
85 | val gOffset = Offset(segmentWidth, segmentLength + segmentWidth)
86 |
87 | val spacingRatioLimited = spacingRatio.coerceIn(0f..0.9f)
88 |
89 | drawHorizontalSegment(led.signal(decoder.a), segmentStyle, aOffset, horizontalSegmentSize, spacingRatioLimited)
90 | drawVerticalSegment(led.signal(decoder.b), segmentStyle, bOffset, verticalSegmentSize, spacingRatioLimited)
91 | drawVerticalSegment(led.signal(decoder.c), segmentStyle, cOffset, verticalSegmentSize, spacingRatioLimited)
92 | drawHorizontalSegment(led.signal(decoder.d), segmentStyle, dOffset, horizontalSegmentSize, spacingRatioLimited)
93 | drawVerticalSegment(led.signal(decoder.e), segmentStyle, eOffset, verticalSegmentSize, spacingRatioLimited)
94 | drawVerticalSegment(led.signal(decoder.f), segmentStyle, fOffset, verticalSegmentSize, spacingRatioLimited)
95 | drawHorizontalSegment(led.signal(decoder.g), segmentStyle, gOffset, horizontalSegmentSize, spacingRatioLimited)
96 | }
97 | }
98 |
99 | private fun DrawScope.drawHorizontalSegment(color: Color, segmentStyle: SegmentStyle, offset: Offset, size: Size, spacingRatio: Float) {
100 | val radius = size.minDimension / 2
101 | val centerX: Float = offset.x + size.width / 2
102 | val centerY: Float = offset.y + size.height / 2
103 | val spacing = radius * spacingRatio
104 | val hexagonPath = Path().apply {
105 | moveTo(centerX, centerY + radius - spacing)
106 | lineTo(centerX - size.width/2, centerY + radius - spacing)
107 | lineTo(centerX - size.width/2 - radius + spacing, centerY)
108 | lineTo(centerX - size.width/2, centerY - radius + spacing)
109 | lineTo(centerX + size.width/2, centerY - radius + spacing)
110 | lineTo(centerX + size.width/2 + radius - spacing, centerY)
111 | lineTo(centerX + size.width/2, centerY + radius - spacing)
112 | }
113 | when(segmentStyle) {
114 | SegmentStyle.FLAT -> drawPath(hexagonPath, color)
115 | SegmentStyle.DIFFUSER -> drawPath(hexagonPath, color.toLedBrush(Offset(centerX, centerY)))
116 | }
117 | }
118 |
119 | private fun DrawScope.drawVerticalSegment(color: Color, segmentStyle: SegmentStyle, offset: Offset, size: Size, spacingRatio: Float) {
120 | val radius = size.minDimension / 2
121 | val centerX: Float = offset.x + size.width / 2
122 | val centerY: Float = offset.y + size.height / 2
123 | val spacing = radius * spacingRatio
124 | val hexagonPath = Path().apply {
125 | moveTo(centerX, centerY + radius + size.height/2 - spacing)
126 | lineTo(centerX - radius + spacing, centerY + size.height/2)
127 | lineTo(centerX - radius + spacing, centerY - size.height/2)
128 | lineTo(centerX, centerY - radius - size.height/2 + spacing)
129 | lineTo(centerX + radius - spacing, centerY - size.height/2)
130 | lineTo(centerX + radius - spacing, centerY + size.height/2)
131 | }
132 | when(segmentStyle) {
133 | SegmentStyle.FLAT -> drawPath(hexagonPath, color)
134 | SegmentStyle.DIFFUSER -> drawPath(hexagonPath, color.toLedBrush(Offset(centerX, centerY)))
135 | }
136 | }
137 |
138 | @Composable
139 | fun DigitalClock(
140 | modifier: Modifier = Modifier,
141 | hourFirst: Int = 0,
142 | hourSecond: Int = 0,
143 | minuteFirst: Int = 0,
144 | minuteSecond: Int = 0,
145 | secondFirst: Int = 0,
146 | secondSecond: Int = 0,
147 | delimiterSignal: Int = 0
148 | ) {
149 | Row(modifier = modifier) {
150 | SegmentDisplay(
151 | modifier = Modifier
152 | .weight(1f)
153 | .padding(4.dp),
154 | decoder = BinaryDecoder(hourFirst),
155 | segmentStyle = SegmentStyle.DIFFUSER
156 | )
157 | SegmentDisplay(
158 | modifier = Modifier
159 | .weight(1f)
160 | .padding(4.dp),
161 | decoder = BinaryDecoder(hourSecond),
162 | segmentStyle = SegmentStyle.DIFFUSER
163 | )
164 | Delimiter(modifier = Modifier
165 | .weight(1f)
166 | .padding(4.dp),
167 | decoder = DelimiterBinaryDecoder(delimiterSignal)
168 | )
169 | SegmentDisplay(
170 | modifier = Modifier
171 | .weight(1f)
172 | .padding(4.dp),
173 | decoder = BinaryDecoder(minuteFirst),
174 | segmentStyle = SegmentStyle.DIFFUSER
175 | )
176 | SegmentDisplay(
177 | modifier = Modifier
178 | .weight(1f)
179 | .padding(4.dp),
180 | decoder = BinaryDecoder(minuteSecond),
181 | segmentStyle = SegmentStyle.DIFFUSER
182 | )
183 | Delimiter(modifier = Modifier
184 | .weight(1f)
185 | .padding(4.dp),
186 | decoder = DelimiterBinaryDecoder(delimiterSignal)
187 | )
188 | SegmentDisplay(
189 | modifier = Modifier
190 | .weight(1f)
191 | .padding(4.dp),
192 | decoder = BinaryDecoder(secondFirst),
193 | segmentStyle = SegmentStyle.DIFFUSER
194 | )
195 | SegmentDisplay(
196 | modifier = Modifier
197 | .weight(1f)
198 | .padding(4.dp),
199 | decoder = BinaryDecoder(secondSecond),
200 | segmentStyle = SegmentStyle.DIFFUSER
201 | )
202 | }
203 | }
204 |
205 | private val defaultLed = SingleColorLed(Color.Red, Color.DarkGray.copy(alpha = 0.3f))
206 |
207 | private const val diffuserFactor = 0.4f
208 |
209 | private fun Color.toLedBrush(offset: Offset) = Brush.radialGradient(
210 | colors = listOf(
211 | this, copy(
212 | red = red * diffuserFactor,
213 | green = green * diffuserFactor,
214 | blue = blue * diffuserFactor
215 | )
216 | ),
217 | center = offset
218 | )
--------------------------------------------------------------------------------
/app/src/test/java/com/rabross/segmenteddisplay/app/ByteBufferSevenSegmentIndexMapperTests.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.app
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.FixMethodOrder
5 | import org.junit.Test
6 | import org.junit.runners.MethodSorters
7 | import java.security.InvalidParameterException
8 |
9 | @FixMethodOrder(MethodSorters.JVM)
10 | internal class ByteBufferSevenSegmentIndexMapperTests {
11 |
12 | private val minBufferWidth = 2
13 | private val minBufferHeight = 5
14 | private val minSevenSegmentCountX = 1
15 | private val minSevenSegmentCountY = 1
16 | private val minSevenSegmentIndex = 0
17 |
18 | @Test
19 | internal fun `Minimum - Segment A`() {
20 | val segmentAIndex = 0
21 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
22 | minBufferWidth,
23 | minBufferHeight,
24 | minSevenSegmentCountX,
25 | minSevenSegmentCountY,
26 | minSevenSegmentIndex,
27 | segmentAIndex
28 | )
29 | assertEquals(0, bufferIndex)
30 | }
31 |
32 | @Test
33 | internal fun `Minimum - Segment B`() {
34 | val segmentBIndex = 1
35 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
36 | minBufferWidth,
37 | minBufferHeight,
38 | minSevenSegmentCountX,
39 | minSevenSegmentCountY,
40 | minSevenSegmentIndex,
41 | segmentBIndex
42 | )
43 | assertEquals(3, bufferIndex)
44 | }
45 |
46 | @Test
47 | internal fun `Minimum - Segment C`() {
48 | val segmentCIndex = 2
49 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
50 | minBufferWidth,
51 | minBufferHeight,
52 | minSevenSegmentCountX,
53 | minSevenSegmentCountY,
54 | minSevenSegmentIndex,
55 | segmentCIndex
56 | )
57 | assertEquals(7, bufferIndex)
58 | }
59 |
60 | @Test
61 | internal fun `Minimum - Segment D`() {
62 | val segmentDIndex = 3
63 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
64 | minBufferWidth,
65 | minBufferHeight,
66 | minSevenSegmentCountX,
67 | minSevenSegmentCountY,
68 | minSevenSegmentIndex,
69 | segmentDIndex
70 | )
71 | assertEquals(8, bufferIndex)
72 | }
73 |
74 | @Test
75 | internal fun `Minimum - Segment E`() {
76 | val segmentEIndex = 4
77 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
78 | minBufferWidth,
79 | minBufferHeight,
80 | minSevenSegmentCountX,
81 | minSevenSegmentCountY,
82 | minSevenSegmentIndex,
83 | segmentEIndex
84 | )
85 | assertEquals(6, bufferIndex)
86 | }
87 |
88 | @Test
89 | internal fun `Minimum - Segment F`() {
90 | val segmentFIndex = 5
91 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
92 | minBufferWidth,
93 | minBufferHeight,
94 | minSevenSegmentCountX,
95 | minSevenSegmentCountY,
96 | minSevenSegmentIndex,
97 | segmentFIndex
98 | )
99 | assertEquals(2, bufferIndex)
100 | }
101 |
102 | @Test
103 | internal fun `Minimum - Segment G`() {
104 | val segmentGIndex = 6
105 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
106 | minBufferWidth,
107 | minBufferHeight,
108 | minSevenSegmentCountX,
109 | minSevenSegmentCountY,
110 | minSevenSegmentIndex,
111 | segmentGIndex
112 | )
113 | assertEquals(4, bufferIndex)
114 | }
115 |
116 | @Test
117 | internal fun `Square Grid - Index 0 Segment A`() {
118 | val sevenSegmentCountX = 2
119 | val sevenSegmentCountY = 2
120 | val bufferWidth = 2 * sevenSegmentCountX
121 | val bufferHeight = 5 * sevenSegmentCountY
122 | val sevenSegmentIndex = 0
123 | val segmentIndex = 0
124 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
125 | bufferWidth,
126 | bufferHeight,
127 | sevenSegmentCountX,
128 | sevenSegmentCountY,
129 | sevenSegmentIndex,
130 | segmentIndex
131 | )
132 | assertEquals(0, bufferIndex)
133 | }
134 |
135 | @Test
136 | internal fun `Square Grid - Index 1 Segment A`() {
137 | val sevenSegmentCountX = 2
138 | val sevenSegmentCountY = 2
139 | val bufferWidth = 2 * sevenSegmentCountX
140 | val bufferHeight = 5 * sevenSegmentCountY
141 | val sevenSegmentIndex = 1
142 | val segmentIndex = 0
143 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
144 | bufferWidth,
145 | bufferHeight,
146 | sevenSegmentCountX,
147 | sevenSegmentCountY,
148 | sevenSegmentIndex,
149 | segmentIndex
150 | )
151 | assertEquals(2, bufferIndex)
152 | }
153 |
154 | @Test
155 | internal fun `Square Grid - Index 2 Segment A`() {
156 | val sevenSegmentCountX = 2
157 | val sevenSegmentCountY = 2
158 | val bufferWidth = 2 * sevenSegmentCountX
159 | val bufferHeight = 5 * sevenSegmentCountY
160 | val sevenSegmentIndex = 2
161 | val segmentIndex = 0
162 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
163 | bufferWidth,
164 | bufferHeight,
165 | sevenSegmentCountX,
166 | sevenSegmentCountY,
167 | sevenSegmentIndex,
168 | segmentIndex
169 | )
170 | assertEquals(20, bufferIndex)
171 | }
172 |
173 | @Test
174 | internal fun `Square Grid - Index 3 Segment A`() {
175 | val sevenSegmentCountX = 2
176 | val sevenSegmentCountY = 2
177 | val bufferWidth = 2 * sevenSegmentCountX
178 | val bufferHeight = 5 * sevenSegmentCountY
179 | val sevenSegmentIndex = 3
180 | val segmentIndex = 0
181 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
182 | bufferWidth,
183 | bufferHeight,
184 | sevenSegmentCountX,
185 | sevenSegmentCountY,
186 | sevenSegmentIndex,
187 | segmentIndex
188 | )
189 | assertEquals(22, bufferIndex)
190 | }
191 |
192 | @Test
193 | internal fun `Square Grid - Index 0 Segment B`() {
194 | val sevenSegmentCountX = 2
195 | val sevenSegmentCountY = 2
196 | val bufferWidth = 2 * sevenSegmentCountX
197 | val bufferHeight = 5 * sevenSegmentCountY
198 | val sevenSegmentIndex = 0
199 | val segmentIndex = 1
200 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
201 | bufferWidth,
202 | bufferHeight,
203 | sevenSegmentCountX,
204 | sevenSegmentCountY,
205 | sevenSegmentIndex,
206 | segmentIndex
207 | )
208 | assertEquals(5, bufferIndex)
209 | }
210 |
211 | @Test
212 | internal fun `Square Grid - Index 1 Segment B`() {
213 | val sevenSegmentCountX = 2
214 | val sevenSegmentCountY = 2
215 | val bufferWidth = 2 * sevenSegmentCountX
216 | val bufferHeight = 5 * sevenSegmentCountY
217 | val sevenSegmentIndex = 1
218 | val segmentIndex = 1
219 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
220 | bufferWidth,
221 | bufferHeight,
222 | sevenSegmentCountX,
223 | sevenSegmentCountY,
224 | sevenSegmentIndex,
225 | segmentIndex
226 | )
227 | assertEquals(7, bufferIndex)
228 | }
229 |
230 | @Test
231 | internal fun `Square Grid - Index 2 Segment B`() {
232 | val sevenSegmentCountX = 2
233 | val sevenSegmentCountY = 2
234 | val bufferWidth = 2 * sevenSegmentCountX
235 | val bufferHeight = 5 * sevenSegmentCountY
236 | val sevenSegmentIndex = 2
237 | val segmentIndex = 1
238 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
239 | bufferWidth,
240 | bufferHeight,
241 | sevenSegmentCountX,
242 | sevenSegmentCountY,
243 | sevenSegmentIndex,
244 | segmentIndex
245 | )
246 | assertEquals(25, bufferIndex)
247 | }
248 |
249 | @Test
250 | internal fun `Square Grid - Index 3 Segment B`() {
251 | val sevenSegmentCountX = 2
252 | val sevenSegmentCountY = 2
253 | val bufferWidth = 2 * sevenSegmentCountX
254 | val bufferHeight = 5 * sevenSegmentCountY
255 | val sevenSegmentIndex = 3
256 | val segmentIndex = 1
257 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
258 | bufferWidth,
259 | bufferHeight,
260 | sevenSegmentCountX,
261 | sevenSegmentCountY,
262 | sevenSegmentIndex,
263 | segmentIndex
264 | )
265 | assertEquals(27, bufferIndex)
266 | }
267 |
268 | @Test
269 | internal fun `Rectangle Grid - Index 3 Segment 0`() {
270 | val sevenSegmentCountX = 5
271 | val sevenSegmentCountY = 3
272 | val bufferWidth = 2 * sevenSegmentCountX
273 | val bufferHeight = 5 * sevenSegmentCountY
274 | val sevenSegmentIndex = 3
275 | val segmentIndex = 0
276 | val bufferIndex = mapSingleSegmentToALPHA8BufferIndex(
277 | bufferWidth,
278 | bufferHeight,
279 | sevenSegmentCountX,
280 | sevenSegmentCountY,
281 | sevenSegmentIndex,
282 | segmentIndex
283 | )
284 | assertEquals(6, bufferIndex)
285 | }
286 |
287 | @Test(expected = InvalidParameterException::class)
288 | internal fun `Invalid buffer format throws error`() {
289 | mapSingleSegmentToALPHA8BufferIndex(3,5,1,1,0,0)
290 | }
291 |
292 | @Test(expected = InvalidParameterException::class)
293 | internal fun `Invalid buffer width throws error`() {
294 | mapSingleSegmentToALPHA8BufferIndex(2,5,2,1,0,0)
295 | }
296 |
297 | @Test(expected = InvalidParameterException::class)
298 | internal fun `Invalid buffer height throws error`() {
299 | mapSingleSegmentToALPHA8BufferIndex(2,5,1,2,0,0)
300 | }
301 | }
--------------------------------------------------------------------------------
/segmenteddisplay/src/main/java/com/rabross/segmenteddisplay/fourteen/Composable.kt:
--------------------------------------------------------------------------------
1 | package com.rabross.segmenteddisplay.fourteen
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.material.Surface
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.geometry.Offset
9 | import androidx.compose.ui.geometry.Size
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.Path
12 | import androidx.compose.ui.graphics.drawscope.DrawScope
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import androidx.compose.ui.unit.dp
15 | import com.rabross.segmenteddisplay.delimiter.BinaryDecoder as DelimiterBinaryDecoder
16 | import com.rabross.segmenteddisplay.Led
17 | import com.rabross.segmenteddisplay.SingleColorLed
18 | import com.rabross.segmenteddisplay.delimiter.Delimiter
19 | import kotlin.math.pow
20 | import kotlin.math.sqrt
21 |
22 | @Preview
23 | @Composable
24 | fun FourteenSegmentDisplayPreview() {
25 | Surface {
26 | SegmentDisplay()
27 | }
28 | }
29 |
30 | @Composable
31 | fun DigitalClock(
32 | modifier: Modifier = Modifier,
33 | hourFirst: Int = 0,
34 | hourSecond: Int = 0,
35 | minuteFirst: Int = 0,
36 | minuteSecond: Int = 0,
37 | secondFirst: Int = 0,
38 | secondSecond: Int = 0,
39 | delimiterSignal: Int = 0
40 | ) {
41 | Row(modifier = modifier) {
42 | SegmentDisplay(modifier = modifier.padding(4.dp),
43 | decoder = BinaryDecoder(hourFirst)
44 | )
45 | SegmentDisplay(modifier = modifier.padding(4.dp),
46 | decoder = BinaryDecoder(hourSecond)
47 | )
48 | Delimiter(modifier = modifier.padding(4.dp),
49 | decoder = DelimiterBinaryDecoder(delimiterSignal)
50 | )
51 | SegmentDisplay(modifier = modifier.padding(4.dp),
52 | decoder = BinaryDecoder(minuteFirst)
53 | )
54 | SegmentDisplay(modifier = modifier.padding(4.dp),
55 | decoder = BinaryDecoder(minuteSecond)
56 | )
57 | Delimiter(modifier = modifier.padding(4.dp),
58 | decoder = DelimiterBinaryDecoder(delimiterSignal)
59 | )
60 | SegmentDisplay(modifier = modifier.padding(4.dp),
61 | decoder = BinaryDecoder(secondFirst)
62 | )
63 | SegmentDisplay(modifier = modifier.padding(4.dp),
64 | decoder = BinaryDecoder(secondSecond)
65 | )
66 | }
67 | }
68 |
69 | @Composable
70 | fun SegmentDisplay(
71 | modifier: Modifier = Modifier,
72 | segmentScale: Int = 4,
73 | led: Led = SingleColorLed(Color.Red, Color.DarkGray.copy(alpha = 0.4f)),
74 | decoder: Decoder = BinaryDecoder()
75 | ) {
76 | Canvas(modifier = modifier
77 | .aspectRatio((1f + segmentScale + 1f) / (1f + segmentScale + 1f + segmentScale + 1f), true)
78 | .size(100.dp)) {
79 |
80 | val scaleWidth = 1 + segmentScale + 1
81 | val scaleHeight = 1 + segmentScale + 1 + segmentScale + 1
82 |
83 | val segmentWidthByWidth = size.width / scaleWidth
84 | val segmentWidthByHeight = size.height / scaleHeight
85 |
86 | val segmentWidth = if (scaleHeight * segmentWidthByWidth < size.height) segmentWidthByWidth else segmentWidthByHeight
87 | val segmentLength = segmentScale * segmentWidth
88 |
89 | val fullWidth = segmentWidth * scaleWidth
90 | val quarterGapWidth = (fullWidth - segmentWidth * 3)/2
91 |
92 | val horizontalSegmentSize = Size(segmentLength, segmentWidth)
93 | val smallHorizontalSegmentSize = Size(segmentLength / 2, segmentWidth)
94 | val verticalSegmentSize = Size(segmentWidth, segmentLength)
95 | val smallVerticalSegmentSize = Size(segmentWidth, segmentLength - segmentWidth/2)
96 | val diagonalSegmentSize = Size(quarterGapWidth, segmentLength)
97 |
98 | val aOffset = Offset(segmentWidth, 0f)
99 | val bOffset = Offset(segmentLength + segmentWidth, segmentWidth)
100 | val cOffset = Offset(segmentLength + segmentWidth, segmentLength + segmentWidth * 2)
101 | val dOffset = Offset(segmentWidth, segmentLength + segmentWidth + segmentLength + segmentWidth)
102 | val eOffset = Offset(0f, segmentLength + segmentWidth * 2)
103 | val fOffset = Offset(0f, segmentWidth)
104 | val g1Offset = Offset(segmentWidth, segmentLength + segmentWidth)
105 | val g2Offset = Offset(quarterGapWidth + segmentWidth * 2, segmentLength + segmentWidth)
106 | val hOffset = Offset(segmentWidth, segmentWidth)
107 | val iOffset = Offset(quarterGapWidth + segmentWidth, segmentWidth + segmentWidth / 2)
108 | val jOffset = Offset(quarterGapWidth + segmentWidth * 2, segmentWidth)
109 | val kOffset = Offset(segmentWidth, segmentLength + segmentWidth * 2)
110 | val lOffset = Offset(quarterGapWidth + segmentWidth, segmentLength + segmentWidth * 2)
111 | val mOffset = Offset(quarterGapWidth + segmentWidth * 2, segmentLength + segmentWidth * 2)
112 |
113 | drawSegmentA(led.signal(decoder.a), aOffset, horizontalSegmentSize)
114 | drawSegmentB(led.signal(decoder.b), bOffset, verticalSegmentSize)
115 | drawSegmentC(led.signal(decoder.c), cOffset, verticalSegmentSize)
116 | drawSegmentD(led.signal(decoder.d), dOffset, horizontalSegmentSize)
117 | drawSegmentE(led.signal(decoder.e), eOffset, verticalSegmentSize)
118 | drawSegmentF(led.signal(decoder.f), fOffset, verticalSegmentSize)
119 | drawSegmentG1(led.signal(decoder.g1), g1Offset, smallHorizontalSegmentSize)
120 | drawSegmentG2(led.signal(decoder.g2), g2Offset, smallHorizontalSegmentSize)
121 | drawSegmentH(led.signal(decoder.h), segmentWidth, hOffset, diagonalSegmentSize)
122 | drawSegmentI(led.signal(decoder.i), iOffset, smallVerticalSegmentSize)
123 | drawSegmentJ(led.signal(decoder.j), segmentWidth, jOffset, diagonalSegmentSize)
124 | drawSegmentK(led.signal(decoder.k), segmentWidth, kOffset, diagonalSegmentSize)
125 | drawSegmentL(led.signal(decoder.l), lOffset, smallVerticalSegmentSize)
126 | drawSegmentM(led.signal(decoder.m), segmentWidth, mOffset, diagonalSegmentSize)
127 | }
128 | }
129 |
130 | private fun DrawScope.drawSegmentA(c: Color, o: Offset, s: Size) = drawHorizontalSegment(c, o, s)
131 | private fun DrawScope.drawSegmentB(c: Color, o: Offset, s: Size) = drawVerticalSegment(c, o, s)
132 | private fun DrawScope.drawSegmentC(c: Color, o: Offset, s: Size) = drawVerticalSegment(c, o, s)
133 | private fun DrawScope.drawSegmentD(c: Color, o: Offset, s: Size) = drawHorizontalSegment(c, o, s)
134 | private fun DrawScope.drawSegmentE(c: Color, o: Offset, s: Size) = drawVerticalSegment(c, o, s)
135 | private fun DrawScope.drawSegmentF(c: Color, o: Offset, s: Size) = drawVerticalSegment(c, o, s)
136 | private fun DrawScope.drawSegmentG1(c: Color, o: Offset, s: Size) = drawSmallHorizontalSegment(c, o, s)
137 | private fun DrawScope.drawSegmentG2(c: Color, o: Offset, s: Size) = drawSmallHorizontalSegment(c, o, s)
138 | private fun DrawScope.drawSegmentH(c: Color, w: Float, o: Offset, s: Size) = drawBackSlashSegment(c, w, o, s)
139 | private fun DrawScope.drawSegmentI(c: Color, o: Offset, s: Size) = drawSmallVerticalSegment(c, o, s)
140 | private fun DrawScope.drawSegmentJ(c: Color, w: Float, o: Offset, s: Size) = drawForwardSlashSegment(c, w, o, s)
141 | private fun DrawScope.drawSegmentK(c: Color, w: Float, o: Offset, s: Size) = drawForwardSlashSegment(c, w, o, s)
142 | private fun DrawScope.drawSegmentL(c: Color, o: Offset, s: Size) = drawSmallVerticalSegment(c, o, s)
143 | private fun DrawScope.drawSegmentM(c: Color, w: Float, o: Offset, s: Size) = drawBackSlashSegment(c, w, o, s)
144 |
145 | private fun DrawScope.drawHorizontalSegment(color: Color, offset: Offset, size: Size) {
146 | val radius = size.minDimension / 2
147 | val centerX = offset.x + size.width / 2
148 | val centerY = offset.y + size.height / 2
149 | val spacing = radius / 10
150 |
151 | val hexagonPath = Path().apply {
152 | moveTo(centerX - size.width/2, centerY + radius - spacing)
153 | lineTo(centerX - size.width/2 - radius + spacing, centerY)
154 | lineTo(centerX - size.width/2, centerY - radius + spacing)
155 | lineTo(centerX + size.width/2, centerY - radius + spacing)
156 | lineTo(centerX + size.width/2 + radius - spacing, centerY)
157 | lineTo(centerX + size.width/2, centerY + radius - spacing)
158 | }
159 |
160 | drawPath(hexagonPath, color)
161 | }
162 |
163 | private fun DrawScope.drawVerticalSegment(color: Color, offset: Offset, size: Size) {
164 | val radius = size.minDimension / 2
165 | val centerX = offset.x + size.width / 2
166 | val centerY = offset.y + size.height / 2
167 | val spacing = radius / 10
168 |
169 | val hexagonPath = Path().apply {
170 | moveTo(centerX, centerY + radius + size.height / 2 - spacing)
171 | lineTo(centerX - radius + spacing, centerY + size.height / 2)
172 | lineTo(centerX - radius + spacing, centerY - size.height / 2)
173 | lineTo(centerX, centerY - radius - size.height / 2 + spacing)
174 | lineTo(centerX + radius - spacing, centerY - size.height / 2)
175 | lineTo(centerX + radius - spacing, centerY + size.height / 2)
176 | }
177 |
178 | drawPath(hexagonPath, color)
179 | }
180 |
181 | private fun DrawScope.drawSmallHorizontalSegment(color: Color, offset: Offset, size: Size) {
182 | val radius = size.minDimension / 2
183 | val centerX = offset.x + size.width / 2
184 | val centerY = offset.y + size.height / 2
185 | val spacing = radius / 10
186 |
187 | val hexagonPath = Path().apply {
188 | moveTo(centerX - size.width / 2, centerY + radius - spacing)
189 | lineTo(centerX - size.width / 2 - radius + spacing, centerY)
190 | lineTo(centerX - size.width / 2, centerY - radius + spacing)
191 | lineTo(centerX + size.width / 2 - radius, centerY - radius + spacing)
192 | lineTo(centerX + size.width / 2 - spacing, centerY)
193 | lineTo(centerX + size.width / 2 - radius, centerY + radius - spacing)
194 | }
195 |
196 | drawPath(hexagonPath, color)
197 | }
198 |
199 | private fun DrawScope.drawSmallVerticalSegment(color: Color, offset: Offset, size: Size) {
200 | val radius = size.minDimension / 2
201 | val centerX = offset.x + size.width / 2
202 | val centerY = offset.y + size.height / 2
203 | val spacing = radius / 10
204 |
205 | val hexagonPath = Path().apply {
206 | moveTo(centerX, centerY + radius + size.height / 2 - spacing)
207 | lineTo(centerX - radius + spacing, centerY + size.height / 2)
208 | lineTo(centerX - radius + spacing, centerY - size.height / 2)
209 | lineTo(centerX, centerY - radius - size.height / 2 + spacing)
210 | lineTo(centerX + radius - spacing, centerY - size.height / 2)
211 | lineTo(centerX + radius - spacing, centerY + size.height / 2)
212 | }
213 |
214 | drawPath(hexagonPath, color)
215 | }
216 |
217 | private fun DrawScope.drawBackSlashSegment(color: Color, width: Float, offset: Offset, size: Size) {
218 | val diagonalWidth = sqrt(width.pow(2)/2) * 2
219 | val spacing = diagonalWidth / 40
220 |
221 | val parallelogram = Path().apply {
222 | moveTo(offset.x + spacing, offset.y + spacing)
223 | lineTo(offset.x + size.width - spacing, offset.y + size.height - diagonalWidth)
224 | lineTo(offset.x + size.width - spacing, offset.y + size.height - spacing)
225 | lineTo(offset.x + spacing, offset.y + diagonalWidth)
226 | }
227 |
228 | drawPath(parallelogram, color)
229 | }
230 |
231 | private fun DrawScope.drawForwardSlashSegment(color: Color, width: Float, offset: Offset, size: Size) {
232 | val diagonalWidth = sqrt(width.pow(2)/2) * 2
233 | val spacing = diagonalWidth / 40
234 |
235 | val parallelogram = Path().apply {
236 | moveTo(offset.x + spacing, offset.y + size.height - spacing)
237 | lineTo(offset.x + spacing, offset.y + size.height - diagonalWidth)
238 | lineTo(offset.x + size.width - spacing, offset.y + spacing)
239 | lineTo(offset.x + size.width - spacing, offset.y + diagonalWidth)
240 | }
241 |
242 | drawPath(parallelogram, color)
243 | }
244 |
--------------------------------------------------------------------------------