From 76389539ba4aa399eba7e4c995dfba51df7287a5 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 7 Jan 2024 15:45:02 +0200 Subject: [PATCH 01/52] Groundwork for dynamic layouts configuration --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 13 ++++ .../rdnt/m8face/data/watchface/LayoutStyle.kt | 77 +++++++++++++++++++ .../m8face/data/watchface/WatchFaceData.kt | 1 + .../m8face/editor/WatchFaceConfigActivity.kt | 73 +++++++++++++++--- .../editor/WatchFaceConfigStateHolder.kt | 31 ++++++++ .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 16 ++++ app/src/main/res/values/strings.xml | 5 ++ 7 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 3099c7e..3121d58 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -40,6 +40,7 @@ import androidx.wear.watchface.style.UserStyleSetting import androidx.wear.watchface.style.WatchFaceLayer import dev.rdnt.m8face.data.watchface.AmbientStyle import dev.rdnt.m8face.data.watchface.ColorStyle +import dev.rdnt.m8face.data.watchface.LayoutStyle import dev.rdnt.m8face.data.watchface.SecondsStyle import dev.rdnt.m8face.data.watchface.WatchFaceColorPalette.Companion.convertToWatchFaceColorPalette import dev.rdnt.m8face.data.watchface.WatchFaceData @@ -47,6 +48,7 @@ import dev.rdnt.m8face.utils.AMBIENT_STYLE_SETTING import dev.rdnt.m8face.utils.BIG_AMBIENT_SETTING import dev.rdnt.m8face.utils.COLOR_STYLE_SETTING import dev.rdnt.m8face.utils.HorizontalComplication +import dev.rdnt.m8face.utils.LAYOUT_STYLE_SETTING import dev.rdnt.m8face.utils.MILITARY_TIME_SETTING import dev.rdnt.m8face.utils.SECONDS_STYLE_SETTING import dev.rdnt.m8face.utils.VerticalComplication @@ -318,6 +320,17 @@ class WatchCanvasRenderer( // Loops through user style and applies new values to watchFaceData. for (options in userStyle) { when (options.key.id.toString()) { + LAYOUT_STYLE_SETTING -> { + val listOption = options.value as + UserStyleSetting.ListUserStyleSetting.ListOption + + newWatchFaceData = newWatchFaceData.copy( + layoutStyle = LayoutStyle.getLayoutStyleConfig( + listOption.id.toString() + ), + ) + } + COLOR_STYLE_SETTING -> { val listOption = options.value as UserStyleSetting.ListUserStyleSetting.ListOption diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt new file mode 100644 index 0000000..e954c76 --- /dev/null +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.rdnt.m8face.data.watchface + +import android.content.Context +import android.graphics.drawable.Icon +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.wear.watchface.style.UserStyleSetting +import androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting +import dev.rdnt.m8face.R + +enum class LayoutStyle( + val id: String, + @StringRes val nameResourceId: Int, + @DrawableRes val iconResourceId: Int, +) { + // TODO: @rdnt use SVGs or compress the pngs to conserve wire memory for both + // these and other styles (so that we don't exceed transfer limitations of + // watchface config over BLE) + DEFAULT( + id = "default", + nameResourceId = R.string.default_layout_style_name, + iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon + ), + TEST( // TODO @rdnt remove + id = "test", + nameResourceId = R.string.default_layout_style_name, + iconResourceId = R.drawable.steel_style_icon, + ); + + companion object { + fun getLayoutStyleConfig(id: String): LayoutStyle { + return when (id) { + DEFAULT.id -> DEFAULT + TEST.id -> TEST + else -> DEFAULT + } + } + + fun toOptionList(context: Context): List { + val colorStyleIdAndResourceIdsList = enumValues() + + return colorStyleIdAndResourceIdsList.map { style -> + layoutStyleToListOption(context, style) + } + } + + fun layoutStyleToListOption( + context: Context, + style: LayoutStyle + ): ListUserStyleSetting.ListOption { + return ListUserStyleSetting.ListOption( + UserStyleSetting.Option.Id(style.id), + context.resources, + style.nameResourceId, + Icon.createWithResource( + context, + style.iconResourceId + ) + ) + } + } +} diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt index 62efb91..93e57b9 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt @@ -19,6 +19,7 @@ package dev.rdnt.m8face.data.watchface * Represents all data needed to render an analog watch face. */ data class WatchFaceData( + val layoutStyle: LayoutStyle = LayoutStyle.DEFAULT, val colorStyle: ColorStyle = ColorStyle.LAST_DANCE, val ambientStyle: AmbientStyle = AmbientStyle.OUTLINE, val secondsStyle: SecondsStyle = SecondsStyle.NONE, diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index 0a03f78..60caa00 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -49,8 +49,6 @@ import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color.Companion.Black -import androidx.compose.ui.graphics.Color.Companion.Blue -import androidx.compose.ui.graphics.Color.Companion.Red import androidx.compose.ui.graphics.Color.Companion.Transparent import androidx.compose.ui.graphics.Color.Companion.White import androidx.compose.ui.graphics.ColorFilter @@ -91,6 +89,7 @@ import dev.chrisbanes.snapper.ExperimentalSnapperApi import dev.rdnt.m8face.R import dev.rdnt.m8face.data.watchface.AmbientStyle import dev.rdnt.m8face.data.watchface.ColorStyle +import dev.rdnt.m8face.data.watchface.LayoutStyle import dev.rdnt.m8face.data.watchface.SecondsStyle import dev.rdnt.m8face.theme.WearAppTheme import dev.rdnt.m8face.utils.BOTTOM_COMPLICATION_ID @@ -251,6 +250,10 @@ class WatchFaceConfigActivity : ComponentActivity() { ) } + private val layoutStyles by lazy { + enumValues() + } + private val colorStyles by lazy { enumValues() } @@ -268,6 +271,7 @@ class WatchFaceConfigActivity : ComponentActivity() { lifecycleScope.launch { stateHolder + layoutStyles colorStyles ambientStyles secondsStyles @@ -276,6 +280,7 @@ class WatchFaceConfigActivity : ComponentActivity() { setContent { WatchfaceConfigApp( stateHolder, + layoutStyles, colorStyles, ambientStyles, secondsStyles, @@ -288,6 +293,7 @@ class WatchFaceConfigActivity : ComponentActivity() { @Composable fun WatchfaceConfigApp( stateHolder: WatchFaceConfigStateHolder, + layoutStyles: Array, colorStyles: Array, ambientStyles: Array, secondsStyles: Array, @@ -306,6 +312,8 @@ fun WatchfaceConfigApp( when (val state = uiState) { is WatchFaceConfigStateHolder.EditWatchFaceUiState.Success -> { val bitmap = state.userStylesAndPreview.previewImage.asImageBitmap() + val layoutIndex = + layoutStyles.indexOfFirst { it.id == state.userStylesAndPreview.layoutStyleId } val colorIndex = colorStyles.indexOfFirst { it.id == state.userStylesAndPreview.colorStyleId } val ambientStyleIndex = @@ -323,10 +331,12 @@ fun WatchfaceConfigApp( ) { ConfigScaffold( stateHolder, + layoutStyles, colorStyles, ambientStyles, secondsStyles, bitmap, + layoutIndex, colorIndex, ambientStyleIndex, secondsStyleIndex, @@ -355,10 +365,12 @@ fun WatchfaceConfigApp( @Composable fun ConfigScaffold( stateHolder: WatchFaceConfigStateHolder, + layoutStyles: Array, colorStyles: Array, ambientStyles: Array, secondsStyles: Array, bitmap: ImageBitmap, + layoutIndex: Int, colorIndex: Int, ambientStyleIndex: Int, secondsStyleIndex: Int, @@ -367,10 +379,12 @@ fun ConfigScaffold( ) { Log.d( "Editor", - "ConfigScaffold($colorIndex, $ambientStyleIndex, $militaryTimeEnabled, $bigAmbientEnabled)" + "ConfigScaffold($layoutIndex, $colorIndex, $ambientStyleIndex, $militaryTimeEnabled, $bigAmbientEnabled)" ) - val pagerState = rememberPagerState { 5 } + val pagerState = rememberPagerState ( + pageCount = { 6 } + ) Scaffold( positionIndicator = { @@ -401,24 +415,26 @@ fun ConfigScaffold( val focusRequester0 = remember { FocusRequester() } val focusRequester1 = remember { FocusRequester() } val focusRequester2 = remember { FocusRequester() } + val focusRequester3 = remember { FocusRequester() } HorizontalPager( flingBehavior = PagerDefaults.flingBehavior(state = pagerState), state = pagerState, modifier = Modifier - .onPreRotaryScrollEvent { pagerState.currentPage != 0 && pagerState.currentPage != 1 && pagerState.currentPage != 2 } + .onPreRotaryScrollEvent { pagerState.currentPage != 0 && pagerState.currentPage != 1 && pagerState.currentPage != 2 && pagerState.currentPage != 3 } .zIndex(3f), // don't ask key = { it } ) { page -> when (page) { - 0 -> ColorStyleSelect(focusRequester0, stateHolder, colorStyles, colorIndex) - 1 -> SecondsStyleSelect(focusRequester1, stateHolder, secondsStyles, secondsStyleIndex) - 2 -> AmbientStyleSelect(focusRequester2, stateHolder, ambientStyles, ambientStyleIndex) - 3 -> ComplicationPicker(stateHolder) - 4 -> Options(stateHolder, militaryTimeEnabled, bigAmbientEnabled) + 0 -> LayoutStyleSelect(focusRequester0, stateHolder, layoutStyles, layoutIndex) + 1 -> ColorStyleSelect(focusRequester1, stateHolder, colorStyles, colorIndex) + 2 -> SecondsStyleSelect(focusRequester2, stateHolder, secondsStyles, secondsStyleIndex) + 3 -> AmbientStyleSelect(focusRequester3, stateHolder, ambientStyles, ambientStyleIndex) + 4 -> ComplicationPicker(stateHolder) + 5 -> Options(stateHolder, militaryTimeEnabled, bigAmbientEnabled) } } @@ -504,6 +520,8 @@ fun ConfigScaffold( focusRequester1.requestFocus() } else if (pagerState.currentPage == 2) { focusRequester2.requestFocus() + }else if (pagerState.currentPage == 3) { + focusRequester3.requestFocus() } } } @@ -788,6 +806,41 @@ fun ColorStyleSelect( } +} + +@Composable +fun LayoutStyleSelect( + focusRequester: FocusRequester, + stateHolder: WatchFaceConfigStateHolder, + layoutStyles: Array, + layoutIndex: Int, +) { + Log.d("Editor", "LayoutStyleSelect($layoutIndex)") + + val layoutIdsSize = remember { layoutStyles.size } + + Box( + Modifier + .fillMaxSize() + ) { + ScrollableColumn( + focusRequester, + layoutIdsSize, + 100f, + layoutIndex, + ) { itemIndex -> + val current = + layoutStyles.indexOfFirst { it.id == (stateHolder.uiState.value as WatchFaceConfigStateHolder.EditWatchFaceUiState.Success).userStylesAndPreview.colorStyleId } + if (current != itemIndex) { + Log.d("Editor", "setLayoutStyle(${layoutStyles[itemIndex].id})") + stateHolder.setLayoutStyle(layoutStyles[itemIndex].id) + } + } + + ColorName(stringResource(layoutStyles[layoutIndex].nameResourceId)) + } + + } @Composable diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index 580185d..540a410 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -60,6 +60,7 @@ class WatchFaceConfigStateHolder( private lateinit var editorSession: EditorSession // Keys from Watch Face Data Structure + private lateinit var layoutStyleKey: UserStyleSetting.ListUserStyleSetting private lateinit var colorStyleKey: UserStyleSetting.ListUserStyleSetting private lateinit var ambientStyleKey: UserStyleSetting.ListUserStyleSetting private lateinit var secondsStyleKey: UserStyleSetting.ListUserStyleSetting @@ -99,6 +100,10 @@ class WatchFaceConfigStateHolder( // Loops through user styles and retrieves user editable styles. for (setting in userStyleSchema.userStyleSettings) { when (setting.id.toString()) { + LAYOUT_STYLE_SETTING -> { + layoutStyleKey = setting as UserStyleSetting.ListUserStyleSetting + } + COLOR_STYLE_SETTING -> { colorStyleKey = setting as UserStyleSetting.ListUserStyleSetting } @@ -145,6 +150,9 @@ class WatchFaceConfigStateHolder( complicationsPreviewData ) + val layoutStyle = + userStyle[layoutStyleKey] as UserStyleSetting.ListUserStyleSetting.ListOption + val colorStyle = userStyle[colorStyleKey] as UserStyleSetting.ListUserStyleSetting.ListOption @@ -161,6 +169,7 @@ class WatchFaceConfigStateHolder( userStyle[bigAmbientKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption return UserStylesAndPreview( + layoutStyleId = layoutStyle.id.toString(), colorStyleId = colorStyle.id.toString(), ambientStyleId = ambientStyle.id.toString(), secondsStyleId = secondsStyle.id.toString(), @@ -208,6 +217,27 @@ class WatchFaceConfigStateHolder( ) } + fun setLayoutStyle(layoutStyleId: String) { + val userStyleSettingList = editorSession.userStyleSchema.userStyleSettings + + // Loops over all UserStyleSettings (basically the keys in the map) to find the setting for + // the color style (which contains all the possible options for that style setting). + for (userStyleSetting in userStyleSettingList) { + if (userStyleSetting.id == UserStyleSetting.Id(LAYOUT_STYLE_SETTING)) { + val colorUserStyleSetting = + userStyleSetting as UserStyleSetting.ListUserStyleSetting + + // Loops over the UserStyleSetting.Option colors (all possible values for the key) + // to find the matching option, and if it exists, sets it as the color style. + for (layoutOptions in colorUserStyleSetting.options) { + if (layoutOptions.id.toString() == layoutStyleId) { + setUserStyleOption(layoutStyleKey, layoutOptions) + } + } + } + } + } + fun setColorStyle(colorStyleId: String) { val userStyleSettingList = editorSession.userStyleSchema.userStyleSettings @@ -311,6 +341,7 @@ class WatchFaceConfigStateHolder( } data class UserStylesAndPreview( + val layoutStyleId: String, val colorStyleId: String, val ambientStyleId: String, val secondsStyleId: String, diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index 01770a5..b1e35d0 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -25,12 +25,15 @@ import dev.rdnt.m8face.data.watchface.AmbientStyle import dev.rdnt.m8face.data.watchface.AmbientStyle.Companion.ambientStyleToListOption import dev.rdnt.m8face.data.watchface.ColorStyle import dev.rdnt.m8face.data.watchface.ColorStyle.Companion.colorStyleToListOption +import dev.rdnt.m8face.data.watchface.LayoutStyle +import dev.rdnt.m8face.data.watchface.LayoutStyle.Companion.layoutStyleToListOption import dev.rdnt.m8face.data.watchface.SecondsStyle import dev.rdnt.m8face.data.watchface.SecondsStyle.Companion.secondsStyleToListOption // Keys to matched content in the user style settings. We listen for changes to these // values in the renderer and if new, we will update the database and update the watch face // being rendered. +const val LAYOUT_STYLE_SETTING = "layout_style_setting" const val COLOR_STYLE_SETTING = "color_style_setting" const val AMBIENT_STYLE_SETTING = "ambient_style_setting" const val SECONDS_STYLE_SETTING = "seconds_style_setting" @@ -45,6 +48,18 @@ const val BIG_AMBIENT_SETTING = "big_ambient_setting" fun createUserStyleSchema(context: Context): UserStyleSchema { // 1. Allows user to change the color styles of the watch face (if any are available). + val layoutStyleSetting = + UserStyleSetting.ListUserStyleSetting( + UserStyleSetting.Id(LAYOUT_STYLE_SETTING), + context.resources, + R.string.layout_style_setting, + R.string.layout_style_setting, + null, + LayoutStyle.toOptionList(context), + listOf(WatchFaceLayer.BASE), + defaultOption = layoutStyleToListOption(context, LayoutStyle.DEFAULT) + ) + val colorStyleSetting = UserStyleSetting.ListUserStyleSetting( UserStyleSetting.Id(COLOR_STYLE_SETTING), @@ -105,6 +120,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { // 4. Create style settings to hold all options. return UserStyleSchema( listOf( + layoutStyleSetting, colorStyleSetting, ambientStyleSetting, secondsStyleSetting, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fed0f35..4239511 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,11 +19,13 @@ m8face + Layout Style Color Ambient Style Seconds Indicator + Layout Style Color scheme Ambient mode style Seconds indicator style @@ -90,6 +92,9 @@ Bottom + Default + + From 5b4a23d1739c361248eb6e9176913257111eabd8 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 7 Jan 2024 21:28:46 +0200 Subject: [PATCH 02/52] Use ComplicationSlotsUserStyleSetting for layout selection to limit visible complications --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 2 +- .../rdnt/m8face/data/watchface/LayoutStyle.kt | 23 -------- .../editor/WatchFaceConfigStateHolder.kt | 14 +++-- .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 55 +++++++++++++++---- 4 files changed, 54 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 3121d58..580149d 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -322,7 +322,7 @@ class WatchCanvasRenderer( when (options.key.id.toString()) { LAYOUT_STYLE_SETTING -> { val listOption = options.value as - UserStyleSetting.ListUserStyleSetting.ListOption + UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption newWatchFaceData = newWatchFaceData.copy( layoutStyle = LayoutStyle.getLayoutStyleConfig( diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt index e954c76..d369eb2 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt @@ -50,28 +50,5 @@ enum class LayoutStyle( else -> DEFAULT } } - - fun toOptionList(context: Context): List { - val colorStyleIdAndResourceIdsList = enumValues() - - return colorStyleIdAndResourceIdsList.map { style -> - layoutStyleToListOption(context, style) - } - } - - fun layoutStyleToListOption( - context: Context, - style: LayoutStyle - ): ListUserStyleSetting.ListOption { - return ListUserStyleSetting.ListOption( - UserStyleSetting.Option.Id(style.id), - context.resources, - style.nameResourceId, - Icon.createWithResource( - context, - style.iconResourceId - ) - ) - } } } diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index 540a410..acae298 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -22,6 +22,7 @@ import androidx.wear.watchface.DrawMode import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.editor.EditorSession +import androidx.wear.watchface.style.ExperimentalHierarchicalStyle import androidx.wear.watchface.style.UserStyle import androidx.wear.watchface.style.UserStyleSchema import androidx.wear.watchface.style.UserStyleSetting @@ -60,7 +61,7 @@ class WatchFaceConfigStateHolder( private lateinit var editorSession: EditorSession // Keys from Watch Face Data Structure - private lateinit var layoutStyleKey: UserStyleSetting.ListUserStyleSetting + private lateinit var layoutStyleKey: UserStyleSetting.ComplicationSlotsUserStyleSetting private lateinit var colorStyleKey: UserStyleSetting.ListUserStyleSetting private lateinit var ambientStyleKey: UserStyleSetting.ListUserStyleSetting private lateinit var secondsStyleKey: UserStyleSetting.ListUserStyleSetting @@ -101,7 +102,7 @@ class WatchFaceConfigStateHolder( for (setting in userStyleSchema.userStyleSettings) { when (setting.id.toString()) { LAYOUT_STYLE_SETTING -> { - layoutStyleKey = setting as UserStyleSetting.ListUserStyleSetting + layoutStyleKey = setting as UserStyleSetting.ComplicationSlotsUserStyleSetting } COLOR_STYLE_SETTING -> { @@ -151,7 +152,7 @@ class WatchFaceConfigStateHolder( ) val layoutStyle = - userStyle[layoutStyleKey] as UserStyleSetting.ListUserStyleSetting.ListOption + userStyle[layoutStyleKey] as UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption val colorStyle = userStyle[colorStyleKey] as UserStyleSetting.ListUserStyleSetting.ListOption @@ -219,17 +220,18 @@ class WatchFaceConfigStateHolder( fun setLayoutStyle(layoutStyleId: String) { val userStyleSettingList = editorSession.userStyleSchema.userStyleSettings + // TODO @rdnt editorSession.userStyleSchema.rootUserStyleSettings // Loops over all UserStyleSettings (basically the keys in the map) to find the setting for // the color style (which contains all the possible options for that style setting). for (userStyleSetting in userStyleSettingList) { if (userStyleSetting.id == UserStyleSetting.Id(LAYOUT_STYLE_SETTING)) { - val colorUserStyleSetting = - userStyleSetting as UserStyleSetting.ListUserStyleSetting + val layoutUserStyleSetting = + userStyleSetting as UserStyleSetting.ComplicationSlotsUserStyleSetting // Loops over the UserStyleSetting.Option colors (all possible values for the key) // to find the matching option, and if it exists, sets it as the color style. - for (layoutOptions in colorUserStyleSetting.options) { + for (layoutOptions in layoutUserStyleSetting.options) { if (layoutOptions.id.toString() == layoutStyleId) { setUserStyleOption(layoutStyleKey, layoutOptions) } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index b1e35d0..c568094 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -19,6 +19,8 @@ import android.content.Context import android.text.format.DateFormat import androidx.wear.watchface.style.UserStyleSchema import androidx.wear.watchface.style.UserStyleSetting +import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting +import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption import androidx.wear.watchface.style.WatchFaceLayer import dev.rdnt.m8face.R import dev.rdnt.m8face.data.watchface.AmbientStyle @@ -26,7 +28,6 @@ import dev.rdnt.m8face.data.watchface.AmbientStyle.Companion.ambientStyleToListO import dev.rdnt.m8face.data.watchface.ColorStyle import dev.rdnt.m8face.data.watchface.ColorStyle.Companion.colorStyleToListOption import dev.rdnt.m8face.data.watchface.LayoutStyle -import dev.rdnt.m8face.data.watchface.LayoutStyle.Companion.layoutStyleToListOption import dev.rdnt.m8face.data.watchface.SecondsStyle import dev.rdnt.m8face.data.watchface.SecondsStyle.Companion.secondsStyleToListOption @@ -49,15 +50,49 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { // 1. Allows user to change the color styles of the watch face (if any are available). val layoutStyleSetting = - UserStyleSetting.ListUserStyleSetting( - UserStyleSetting.Id(LAYOUT_STYLE_SETTING), - context.resources, - R.string.layout_style_setting, - R.string.layout_style_setting, - null, - LayoutStyle.toOptionList(context), - listOf(WatchFaceLayer.BASE), - defaultOption = layoutStyleToListOption(context, LayoutStyle.DEFAULT) + ComplicationSlotsUserStyleSetting( + id = UserStyleSetting.Id(LAYOUT_STYLE_SETTING), + resources = context.resources, + displayNameResourceId = R.string.layout_style_setting, + descriptionResourceId = R.string.layout_style_setting_description, + icon = null, + complicationConfig = listOf( + ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.DEFAULT.id), + resources = context.resources, + displayNameResourceId = R.string.watchface_complications_setting_both, + icon = null, + // NB this list is empty because each [ComplicationSlotOverlay] is applied on + // top of the initial config. + complicationSlotOverlays = listOf() + ), + ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.TEST.id), + resources = context.resources, + displayNameResourceId = R.string.watchface_complications_setting_none, + icon = null, + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + LEFT_COMPLICATION_ID, + enabled = false + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + RIGHT_COMPLICATION_ID, + enabled = false + ) + ) + ), + ), + listOf(WatchFaceLayer.COMPLICATIONS), + defaultOption = ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.DEFAULT.id), + resources = context.resources, + displayNameResourceId = R.string.watchface_complications_setting_both, + icon = null, + // NB this list is empty because each [ComplicationSlotOverlay] is applied on + // top of the initial config. + complicationSlotOverlays = listOf() + ), ) val colorStyleSetting = From 8b5f1c61c4a08334a7524aa75b7a3681e18a81e9 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 17 Jan 2024 22:33:25 +0200 Subject: [PATCH 03/52] Layout selection on watch and companion --- .../rdnt/m8face/data/watchface/LayoutStyle.kt | 22 +- .../rdnt/m8face/utils/ComplicationUtils.kt | 376 ++++++++++-------- .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 115 +++++- app/src/main/res/values/strings.xml | 12 +- 4 files changed, 330 insertions(+), 195 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt index d369eb2..416395d 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt @@ -36,17 +36,29 @@ enum class LayoutStyle( nameResourceId = R.string.default_layout_style_name, iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon ), - TEST( // TODO @rdnt remove - id = "test", - nameResourceId = R.string.default_layout_style_name, - iconResourceId = R.drawable.steel_style_icon, + FOCUS( + id = "focus", + nameResourceId = R.string.focus_layout_style_name, + iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon + ), + SPORT( + id = "sport", + nameResourceId = R.string.sport_layout_style_name, + iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon + ), + COMPLICATIONS( + id = "complications", + nameResourceId = R.string.complications_layout_style_name, + iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon ); companion object { fun getLayoutStyleConfig(id: String): LayoutStyle { return when (id) { DEFAULT.id -> DEFAULT - TEST.id -> TEST + FOCUS.id -> FOCUS + SPORT.id -> SPORT + COMPLICATIONS.id -> COMPLICATIONS else -> DEFAULT } } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index af24df4..ab4fdb5 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -25,11 +25,9 @@ import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.graphics.RectF import android.util.Log -import androidx.annotation.Keep import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap import androidx.wear.watchface.CanvasComplication -import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.ComplicationSlot import androidx.wear.watchface.ComplicationSlotsManager import androidx.wear.watchface.RenderParameters @@ -38,10 +36,8 @@ import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy import androidx.wear.watchface.complications.SystemDataSources import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType -import androidx.wear.watchface.complications.data.MonochromaticImageComplicationData import androidx.wear.watchface.complications.data.NoDataComplicationData import androidx.wear.watchface.complications.data.ShortTextComplicationData -import androidx.wear.watchface.complications.data.SmallImageComplicationData import androidx.wear.watchface.style.CurrentUserStyleRepository import dev.rdnt.m8face.R import java.time.Instant @@ -93,41 +89,94 @@ internal const val LEFT_COMPLICATION_ID = 100 internal const val RIGHT_COMPLICATION_ID = 101 internal const val TOP_COMPLICATION_ID = 102 internal const val BOTTOM_COMPLICATION_ID = 103 +internal const val TOP_LEFT_COMPLICATION_ID = 104 +internal const val BOTTOM_LEFT_COMPLICATION_ID = 105 +internal const val TOP_RIGHT_COMPLICATION_ID = 106 +internal const val BOTTOM_RIGHT_COMPLICATION_ID = 107 +internal const val LEFT_ICON_COMPLICATION_ID = 108 +internal const val RIGHT_ICON_COMPLICATION_ID = 109 +internal const val RIGHT_TEXT_COMPLICATION_ID = 110 /** * Represents the unique id associated with a complication and the complication types it supports. */ sealed class ComplicationConfig(val id: Int, val supportedTypes: List) { object Left : ComplicationConfig( - LEFT_COMPLICATION_ID, - listOf( + LEFT_COMPLICATION_ID, listOf( ComplicationType.SHORT_TEXT, -// ComplicationType.RANGED_VALUE, ComplicationType.MONOCHROMATIC_IMAGE, ComplicationType.SMALL_IMAGE ) ) object Right : ComplicationConfig( - RIGHT_COMPLICATION_ID, - listOf( + RIGHT_COMPLICATION_ID, listOf( ComplicationType.SHORT_TEXT, -// ComplicationType.RANGED_VALUE, ComplicationType.MONOCHROMATIC_IMAGE, ComplicationType.SMALL_IMAGE ) ) object Top : ComplicationConfig( - TOP_COMPLICATION_ID, - listOf( + TOP_COMPLICATION_ID, listOf( ComplicationType.SHORT_TEXT, ) ) object Bottom : ComplicationConfig( - BOTTOM_COMPLICATION_ID, - listOf( + BOTTOM_COMPLICATION_ID, listOf( + ComplicationType.SHORT_TEXT, + ) + ) + + object TopLeft : ComplicationConfig( + TOP_LEFT_COMPLICATION_ID, listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ) + ) + + object BottomLeft : ComplicationConfig( + BOTTOM_LEFT_COMPLICATION_ID, listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ) + ) + + object TopRight : ComplicationConfig( + TOP_RIGHT_COMPLICATION_ID, listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ) + ) + + object BottomRight : ComplicationConfig( + BOTTOM_RIGHT_COMPLICATION_ID, listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ) + ) + + object LeftIcon : ComplicationConfig( + LEFT_ICON_COMPLICATION_ID, listOf( + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ) + ) + + object RightIcon : ComplicationConfig( + RIGHT_ICON_COMPLICATION_ID, listOf( + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ) + ) + + object RightText : ComplicationConfig( + RIGHT_TEXT_COMPLICATION_ID, listOf( ComplicationType.SHORT_TEXT, ) ) @@ -144,10 +193,7 @@ fun createComplicationSlotManager( canvasComplicationFactory = createVerticalComplicationFactory(context), supportedTypes = ComplicationConfig.Left.supportedTypes, defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( -// SystemDataSources.DATA_SOURCE_DAY_OF_WEEK, -// ComplicationType.SHORT_TEXT - SystemDataSources.NO_DATA_SOURCE, - ComplicationType.SHORT_TEXT + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( @@ -158,17 +204,14 @@ fun createComplicationSlotManager( ) ) ).setNameResourceId(R.string.left_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).build() + .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() val customRightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = ComplicationConfig.Right.id, canvasComplicationFactory = createVerticalComplicationFactory(context), supportedTypes = ComplicationConfig.Right.supportedTypes, defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( -// SystemDataSources.DATA_SOURCE_DAY_OF_WEEK, -// ComplicationType.SHORT_TEXT - SystemDataSources.NO_DATA_SOURCE, - ComplicationType.SHORT_TEXT + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( @@ -179,17 +222,14 @@ fun createComplicationSlotManager( ) ) ).setNameResourceId(R.string.right_complication_name) - .setScreenReaderNameResourceId(R.string.right_complication_name).build() + .setScreenReaderNameResourceId(R.string.right_complication_name).setEnabled(false).build() val customTopComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = ComplicationConfig.Top.id, canvasComplicationFactory = createHorizontalComplicationFactory(context), supportedTypes = ComplicationConfig.Top.supportedTypes, defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( - SystemDataSources.DATA_SOURCE_DATE, - ComplicationType.SHORT_TEXT -// SystemDataSources.NO_DATA_SOURCE, -// ComplicationType.SHORT_TEXT + SystemDataSources.DATA_SOURCE_DATE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( @@ -200,15 +240,14 @@ fun createComplicationSlotManager( ) ) ).setNameResourceId(R.string.top_complication_name) - .setScreenReaderNameResourceId(R.string.top_complication_name).build() + .setScreenReaderNameResourceId(R.string.top_complication_name).setEnabled(false).build() val customBottomComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = ComplicationConfig.Bottom.id, canvasComplicationFactory = createHorizontalComplicationFactory(context), supportedTypes = ComplicationConfig.Bottom.supportedTypes, defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( - SystemDataSources.DATA_SOURCE_WATCH_BATTERY, - ComplicationType.SHORT_TEXT + SystemDataSources.DATA_SOURCE_WATCH_BATTERY, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( @@ -219,159 +258,148 @@ fun createComplicationSlotManager( ) ) ).setNameResourceId(R.string.bottom_complication_name) - .setScreenReaderNameResourceId(R.string.bottom_complication_name).build() + .setScreenReaderNameResourceId(R.string.bottom_complication_name).setEnabled(false).build() - return ComplicationSlotsManager( - listOf( - customLeftComplication, - customRightComplication, - customTopComplication, - customBottomComplication + + val customTopLeftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = ComplicationConfig.TopLeft.id, + canvasComplicationFactory = createVerticalComplicationFactory(context), + supportedTypes = ComplicationConfig.TopLeft.supportedTypes, + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), - currentUserStyleRepository - ) -} + bounds = ComplicationSlotBounds( + RectF( + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND + ) + ) + ).setNameResourceId(R.string.top_left_complication_name) + .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() -class RectangleCanvasComplication(private val context: Context) : CanvasComplication { - override fun render( - canvas: Canvas, - bounds: Rect, - zonedDateTime: ZonedDateTime, - renderParameters: RenderParameters, - slotId: Int - ) { - val start = System.currentTimeMillis() - Log.d("RectangleCanvasComplication", "render($slotId, ${_data.type}) -- start: ${start}ms") - - val dataSource = _data.dataSource - val isBattery = - dataSource?.className == "com.google.android.clockwork.sysui.experiences.complications.providers.BatteryProviderService" - - // debug - val dp = Paint() - dp.color = Color.parseColor("#444444") -// canvas.drawRect(bounds, dp) - - var text: String - var title: String? = null - var icon: Bitmap? = null - var iconRect = Rect(0, 0, 32, 32) - - when (_data.type) { - ComplicationType.SHORT_TEXT -> { - val dat = _data as ShortTextComplicationData - text = dat.text.getTextAt(context.resources, Instant.now()).toString() - - if (dat.monochromaticImage != null) { - val drawable = dat.monochromaticImage!!.image.loadDrawable(context) - if (drawable != null) { - icon = drawable.toBitmap(32, 32) - } - } - - if (dat.title != null) { - title = dat.title!!.getTextAt(context.resources, Instant.now()).toString() - } - - } - - else -> { - Log.d("TIME", "start: ${start}ms, elapsed: ${System.currentTimeMillis() - start}ms") - return - } - } - - if (isBattery) { - val drawable = ContextCompat.getDrawable(context, R.drawable.battery_icon)!! - icon = drawable.toBitmap(30, 15) - iconRect = Rect(-1, 0, 29, 15) - } - -// text = "1234567" - - val tp = Paint() - tp.isAntiAlias = true - tp.textSize = 24F / 384F * canvas.width - tp.typeface = context.resources.getFont(R.font.m8stealth57) - tp.textAlign = Paint.Align.CENTER - tp.color = Color.parseColor("#8888bb") - - var offsetX = - iconRect.width() / 2f + 6f // half icon width to the right plus 3f to the right for some spacing - val offsetY = 10.5f - - var prefixLen = 0 - if (isBattery) { - prefixLen = 3 - text.length - text = text.padStart(3, ' ') - } - - val width = 15f * text.length + 3f * (text.length - 1) - - if (title != null) { - offsetX = 0f - text = "$title $text" - } - - tp.color = Color.parseColor("#8888bb") - - canvas.drawText( - text.uppercase(), - bounds.exactCenterX() + offsetX / 384F * canvas.width.toFloat(), - bounds.exactCenterY() + offsetY / 384F * canvas.height.toFloat(), - tp + val customBottomLeftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = ComplicationConfig.BottomLeft.id, + canvasComplicationFactory = createVerticalComplicationFactory(context), + supportedTypes = ComplicationConfig.BottomLeft.supportedTypes, + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT + ), + bounds = ComplicationSlotBounds( + RectF( + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND + ) ) + ).setNameResourceId(R.string.bottom_left_complication_name) + .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - if (isBattery) { - val prefix = "".padStart(prefixLen, '0') + " ".repeat(3 - prefixLen) + val customTopRightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = ComplicationConfig.TopRight.id, + canvasComplicationFactory = createVerticalComplicationFactory(context), + supportedTypes = ComplicationConfig.TopRight.supportedTypes, + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT + ), + bounds = ComplicationSlotBounds( + RectF( + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND + ) + ) + ).setNameResourceId(R.string.top_right_complication_name) + .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - tp.color = Color.parseColor("#343434") + val customBottomRightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = ComplicationConfig.BottomRight.id, + canvasComplicationFactory = createVerticalComplicationFactory(context), + supportedTypes = ComplicationConfig.BottomRight.supportedTypes, + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT + ), + bounds = ComplicationSlotBounds( + RectF( + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND + ) + ) + ).setNameResourceId(R.string.bottom_right_complication_name) + .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - canvas.drawText( - prefix, - bounds.exactCenterX() + offsetX / 384F * canvas.width.toFloat(), - bounds.exactCenterY() + offsetY / 384F * canvas.height.toFloat(), - tp + val customLeftIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = ComplicationConfig.LeftIcon.id, + canvasComplicationFactory = createVerticalComplicationFactory(context), + supportedTypes = ComplicationConfig.LeftIcon.supportedTypes, + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.MONOCHROMATIC_IMAGE + ), + bounds = ComplicationSlotBounds( + RectF( + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND ) - } + ) + ).setNameResourceId(R.string.left_icon_complication_name) + .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - if (icon != null && title == null) { + val customRightIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = ComplicationConfig.RightIcon.id, + canvasComplicationFactory = createVerticalComplicationFactory(context), + supportedTypes = ComplicationConfig.RightIcon.supportedTypes, + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.MONOCHROMATIC_IMAGE + ), + bounds = ComplicationSlotBounds( + RectF( + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND + ) + ) + ).setNameResourceId(R.string.right_icon_complication_name) + .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - val srcRect = iconRect - val dstRect = RectF( - bounds.exactCenterX() - iconRect.width() / 2f - width / 2 - 6f, - bounds.exactCenterY() - iconRect.height() / 2f, - bounds.exactCenterX() + iconRect.width() / 2f - width / 2 - 6f, - bounds.exactCenterY() + iconRect.height() / 2f, + val customRightTextComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = ComplicationConfig.RightText.id, + canvasComplicationFactory = createVerticalComplicationFactory(context), + supportedTypes = ComplicationConfig.RightText.supportedTypes, + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT + ), + bounds = ComplicationSlotBounds( + RectF( + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND ) + ) + ).setNameResourceId(R.string.right_text_complication_name) + .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - val iconPaint = Paint() - iconPaint.isAntiAlias = false - iconPaint.colorFilter = - PorterDuffColorFilter(Color.parseColor("#8888bb"), PorterDuff.Mode.SRC_IN) - canvas.drawBitmap(icon, srcRect, dstRect, iconPaint) - } - - Log.d("TIME", "start: ${start}ms, elapsed: ${System.currentTimeMillis() - start}ms") - } - - override fun drawHighlight( - canvas: Canvas, - bounds: Rect, - boundsType: Int, - zonedDateTime: ZonedDateTime, - color: Int - ) { - } - - private var _data: ComplicationData = NoDataComplicationData() - - override fun getData(): ComplicationData = _data - - override fun loadData( - complicationData: ComplicationData, - loadDrawablesAsynchronous: Boolean - ) { - _data = complicationData - } + return ComplicationSlotsManager( + listOf( + customLeftComplication, + customRightComplication, + customTopComplication, + customBottomComplication, + customTopLeftComplication, + customBottomLeftComplication, + customTopRightComplication, + customBottomRightComplication, + customLeftIconComplication, + customRightIconComplication, + customRightTextComplication + ), currentUserStyleRepository + ) } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index c568094..1c1cbac 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -60,26 +60,96 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { ComplicationSlotsOption( id = UserStyleSetting.Option.Id(LayoutStyle.DEFAULT.id), resources = context.resources, - displayNameResourceId = R.string.watchface_complications_setting_both, + displayNameResourceId = R.string.default_layout_style_name, icon = null, - // NB this list is empty because each [ComplicationSlotOverlay] is applied on - // top of the initial config. - complicationSlotOverlays = listOf() + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + RIGHT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ) ), + ComplicationSlotsOption( - id = UserStyleSetting.Option.Id(LayoutStyle.TEST.id), + id = UserStyleSetting.Option.Id(LayoutStyle.FOCUS.id), resources = context.resources, - displayNameResourceId = R.string.watchface_complications_setting_none, + displayNameResourceId = R.string.focus_layout_style_name, icon = null, complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - LEFT_COMPLICATION_ID, - enabled = false + LEFT_ICON_COMPLICATION_ID, + enabled = true ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - RIGHT_COMPLICATION_ID, - enabled = false - ) + RIGHT_ICON_COMPLICATION_ID, + enabled = true + ), + ) + ), + + ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.SPORT.id), + resources = context.resources, + displayNameResourceId = R.string.sport_layout_style_name, + icon = null, + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + RIGHT_TEXT_COMPLICATION_ID, + enabled = true + ), + ) + ), + + ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.COMPLICATIONS.id), + resources = context.resources, + displayNameResourceId = R.string.complications_layout_style_name, + icon = null, + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_RIGHT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_RIGHT_COMPLICATION_ID, + enabled = true + ), ) ), ), @@ -87,11 +157,26 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { defaultOption = ComplicationSlotsOption( id = UserStyleSetting.Option.Id(LayoutStyle.DEFAULT.id), resources = context.resources, - displayNameResourceId = R.string.watchface_complications_setting_both, + displayNameResourceId = R.string.default_layout_style_name, icon = null, - // NB this list is empty because each [ComplicationSlotOverlay] is applied on - // top of the initial config. - complicationSlotOverlays = listOf() + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + RIGHT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ) ), ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4239511..66fdd11 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -90,9 +90,19 @@ Right Top Bottom - + Top Left + Bottom Left + Top Right + Bottom Right + Left + Right + Right Default + Seconds + Focus + Sport + Complications From ca11364a4b9a8a798760cff165b70ee52d57e1a3 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 17 Jan 2024 23:47:53 +0200 Subject: [PATCH 04/52] Complication and focus layouts complications bounds --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 7 + .../rdnt/m8face/utils/ComplicationUtils.kt | 86 +++++++---- .../dev/rdnt/m8face/utils/IconComplication.kt | 141 ++++++++++++++++++ 3 files changed, 207 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 580149d..ba17132 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -48,6 +48,7 @@ import dev.rdnt.m8face.utils.AMBIENT_STYLE_SETTING import dev.rdnt.m8face.utils.BIG_AMBIENT_SETTING import dev.rdnt.m8face.utils.COLOR_STYLE_SETTING import dev.rdnt.m8face.utils.HorizontalComplication +import dev.rdnt.m8face.utils.IconComplication import dev.rdnt.m8face.utils.LAYOUT_STYLE_SETTING import dev.rdnt.m8face.utils.MILITARY_TIME_SETTING import dev.rdnt.m8face.utils.SECONDS_STYLE_SETTING @@ -467,6 +468,9 @@ class WatchCanvasRenderer( is HorizontalComplication -> (complication.renderer as HorizontalComplication).tertiaryColor = watchFaceColors.tertiaryColor + is IconComplication -> (complication.renderer as IconComplication).tertiaryColor = + watchFaceColors.tertiaryColor + else -> {} } } @@ -638,6 +642,9 @@ class WatchCanvasRenderer( is HorizontalComplication -> (complication.renderer as HorizontalComplication).opacity = opacity + is IconComplication -> (complication.renderer as IconComplication).opacity = + opacity + else -> {} } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index ab4fdb5..d99768c 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -97,6 +97,36 @@ internal const val LEFT_ICON_COMPLICATION_ID = 108 internal const val RIGHT_ICON_COMPLICATION_ID = 109 internal const val RIGHT_TEXT_COMPLICATION_ID = 110 +private const val TOP_LEFT_COMPLICATION_LEFT_BOUND = 27f / 384f +private const val TOP_LEFT_COMPLICATION_TOP_BOUND = 87f / 384f +private const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 27f / 384f + 78f / 384f +private const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 102f / 384f + +private const val BOTTOM_LEFT_COMPLICATION_LEFT_BOUND = 27f / 384f +private const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 195f / 384f +private const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 27f / 384f + 78f / 384f +private const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 195f / 384f + 102f / 384f + +private const val TOP_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f +private const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 87f / 384f +private const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 78f / 384f +private const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 102f / 384f + +private const val BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f +private const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 195f / 384f +private const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 78f / 384f +private const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 195f / 384f + 102f / 384f + +private const val LEFT_ICON_COMPLICATION_LEFT_BOUND = 33f / 384f +private const val LEFT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f +private const val LEFT_ICON_COMPLICATION_RIGHT_BOUND = 33f / 384f + 54f / 384f +private const val LEFT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f + +private const val RIGHT_ICON_COMPLICATION_LEFT_BOUND = 297f / 384f +private const val RIGHT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f +private const val RIGHT_ICON_COMPLICATION_RIGHT_BOUND = 297f / 384f + 54f / 384f +private const val RIGHT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f + /** * Represents the unique id associated with a complication and the complication types it supports. */ @@ -188,6 +218,8 @@ fun createComplicationSlotManager( currentUserStyleRepository: CurrentUserStyleRepository, ): ComplicationSlotsManager { + // TODO @rdnt setNameResourceId setScreenReaderNameResourceId use proper name for all + val customLeftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = ComplicationConfig.Left.id, canvasComplicationFactory = createVerticalComplicationFactory(context), @@ -270,10 +302,10 @@ fun createComplicationSlotManager( ), bounds = ComplicationSlotBounds( RectF( - LEFT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, - LEFT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + TOP_LEFT_COMPLICATION_LEFT_BOUND, + TOP_LEFT_COMPLICATION_TOP_BOUND, + TOP_LEFT_COMPLICATION_RIGHT_BOUND, + TOP_LEFT_COMPLICATION_BOTTOM_BOUND ) ) ).setNameResourceId(R.string.top_left_complication_name) @@ -288,10 +320,10 @@ fun createComplicationSlotManager( ), bounds = ComplicationSlotBounds( RectF( - LEFT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, - LEFT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, + BOTTOM_LEFT_COMPLICATION_TOP_BOUND, + BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, + BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND ) ) ).setNameResourceId(R.string.bottom_left_complication_name) @@ -306,10 +338,10 @@ fun createComplicationSlotManager( ), bounds = ComplicationSlotBounds( RectF( - LEFT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, - LEFT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + TOP_RIGHT_COMPLICATION_LEFT_BOUND, + TOP_RIGHT_COMPLICATION_TOP_BOUND, + TOP_RIGHT_COMPLICATION_RIGHT_BOUND, + TOP_RIGHT_COMPLICATION_BOTTOM_BOUND ) ) ).setNameResourceId(R.string.top_right_complication_name) @@ -324,10 +356,10 @@ fun createComplicationSlotManager( ), bounds = ComplicationSlotBounds( RectF( - LEFT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, - LEFT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, + BOTTOM_RIGHT_COMPLICATION_TOP_BOUND, + BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, + BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND ) ) ).setNameResourceId(R.string.bottom_right_complication_name) @@ -335,17 +367,17 @@ fun createComplicationSlotManager( val customLeftIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = ComplicationConfig.LeftIcon.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), + canvasComplicationFactory = createIconComplicationFactory(context), supportedTypes = ComplicationConfig.LeftIcon.supportedTypes, defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.MONOCHROMATIC_IMAGE ), bounds = ComplicationSlotBounds( RectF( - LEFT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, - LEFT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + LEFT_ICON_COMPLICATION_LEFT_BOUND, + LEFT_ICON_COMPLICATION_TOP_BOUND, + LEFT_ICON_COMPLICATION_RIGHT_BOUND, + LEFT_ICON_COMPLICATION_BOTTOM_BOUND ) ) ).setNameResourceId(R.string.left_icon_complication_name) @@ -353,17 +385,17 @@ fun createComplicationSlotManager( val customRightIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = ComplicationConfig.RightIcon.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), + canvasComplicationFactory = createIconComplicationFactory(context), supportedTypes = ComplicationConfig.RightIcon.supportedTypes, defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.MONOCHROMATIC_IMAGE ), bounds = ComplicationSlotBounds( RectF( - LEFT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, - LEFT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + RIGHT_ICON_COMPLICATION_LEFT_BOUND, + RIGHT_ICON_COMPLICATION_TOP_BOUND, + RIGHT_ICON_COMPLICATION_RIGHT_BOUND, + RIGHT_ICON_COMPLICATION_BOTTOM_BOUND ) ) ).setNameResourceId(R.string.right_icon_complication_name) @@ -371,7 +403,7 @@ fun createComplicationSlotManager( val customRightTextComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = ComplicationConfig.RightText.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), + canvasComplicationFactory = createHorizontalComplicationFactory(context), supportedTypes = ComplicationConfig.RightText.supportedTypes, defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT diff --git a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt new file mode 100644 index 0000000..2654a93 --- /dev/null +++ b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt @@ -0,0 +1,141 @@ +package dev.rdnt.m8face.utils + +import android.content.Context +import android.graphics.* +import android.util.Log +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.withRotation +import androidx.wear.watchface.CanvasComplication +import androidx.wear.watchface.CanvasComplicationFactory +import androidx.wear.watchface.RenderParameters +import androidx.wear.watchface.complications.data.* +import dev.rdnt.m8face.R +import java.time.Instant +import java.time.ZonedDateTime + +class IconComplication(private val context: Context) : CanvasComplication { + var tertiaryColor: Int = Color.parseColor("#8888bb") + set(tertiaryColor) { + field = tertiaryColor + iconPaint.colorFilter = PorterDuffColorFilter(tertiaryColor, PorterDuff.Mode.SRC_IN) + } + + var opacity: Float = 1f + set(opacity) { + field = opacity + + val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, opacity) + + iconPaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + imagePaint.alpha = (opacity * 255).toInt() + } + + private val iconPaint = Paint().apply { + colorFilter = PorterDuffColorFilter(tertiaryColor, PorterDuff.Mode.SRC_IN) + } + + private val imagePaint = Paint() + + override fun render( + canvas: Canvas, + bounds: Rect, + zonedDateTime: ZonedDateTime, + renderParameters: RenderParameters, + slotId: Int + ) { + if (bounds.isEmpty) return + + when (data.type) { + ComplicationType.MONOCHROMATIC_IMAGE -> { + renderMonochromaticImageComplication( + canvas, + bounds, + data as MonochromaticImageComplicationData + ) + } + + ComplicationType.SMALL_IMAGE -> { + renderSmallImageComplication(canvas, bounds, data as SmallImageComplicationData) + } + + else -> return + } + } + + private fun renderMonochromaticImageComplication( + canvas: Canvas, + bounds: Rect, + data: MonochromaticImageComplicationData, + ) { + val icon: Bitmap + val iconBounds: Rect + + val drawable = data.monochromaticImage.image.loadDrawable(context) ?: return + + val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.8f).toInt() + + icon = drawable.toBitmap(size, size) + iconBounds = Rect(0, 0, size, size) + + val dstRect = RectF( + bounds.exactCenterX() - iconBounds.width() / 2, + bounds.exactCenterY() - iconBounds.height() / 2, + bounds.exactCenterX() + iconBounds.width() / 2, + bounds.exactCenterY() + iconBounds.height() / 2, + ) + + canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) + } + + private fun renderSmallImageComplication( + canvas: Canvas, + bounds: Rect, + data: SmallImageComplicationData, + ) { + val icon: Bitmap + val iconBounds: Rect + + val drawable = data.smallImage.image.loadDrawable(context) ?: return + + val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.75f).toInt() + + icon = drawable.toBitmap(size, size) + iconBounds = Rect(0, 0, size, size) + + val dstRect = RectF( + bounds.exactCenterX() - iconBounds.width() / 2, + bounds.exactCenterY() - iconBounds.height() / 2, + bounds.exactCenterX() + iconBounds.width() / 2, + bounds.exactCenterY() + iconBounds.height() / 2, + ) + + canvas.drawBitmap(icon, iconBounds, dstRect, imagePaint) + } + + override fun drawHighlight( + canvas: Canvas, + bounds: Rect, + boundsType: Int, + zonedDateTime: ZonedDateTime, + color: Int + ) { + // Rendering of highlights + } + + private var data: ComplicationData = NoDataComplicationData() + + override fun getData(): ComplicationData = data + + override fun loadData( + complicationData: ComplicationData, + loadDrawablesAsynchronous: Boolean + ) { + data = complicationData + } +} + +fun createIconComplicationFactory(context: Context) = CanvasComplicationFactory { _, _ -> + IconComplication(context) +} From c6ab7c5df5af8689f45147410da181738c4ea6ea Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Fri, 19 Jan 2024 20:33:30 +0200 Subject: [PATCH 05/52] Complication rendering bounds optimizations --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 21 +++++++++--- .../editor/WatchFaceConfigStateHolder.kt | 28 ++++++++++++++++ .../rdnt/m8face/utils/ComplicationUtils.kt | 26 +++++++-------- .../m8face/utils/HorizontalComplication.kt | 4 +++ .../dev/rdnt/m8face/utils/IconComplication.kt | 4 +++ .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 13 ++++---- .../rdnt/m8face/utils/VerticalComplication.kt | 32 +++++++++++-------- 7 files changed, 90 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index ba17132..c892ac1 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -22,6 +22,7 @@ import android.animation.PropertyValuesHolder import android.content.Context import android.graphics.* import android.util.FloatProperty +import android.util.Log import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep @@ -284,12 +285,22 @@ class WatchCanvasRenderer( coroutineScope.launch { watchState.isAmbient.collect { isAmbient -> - if (isAmbient!!) { // you call this readable? come on - ambientExitAnimator.cancel() - drawProperties.timeScale = 0f + if (!watchState.isHeadless) { + if (isAmbient!!) { // you call this readable? come on + ambientExitAnimator.cancel() + drawProperties.timeScale = 0f + } else { + ambientExitAnimator.setupStartValues() + ambientExitAnimator.start() + } } else { - ambientExitAnimator.setupStartValues() - ambientExitAnimator.start() + if (isAmbient!!) { // you call this readable? come on + ambientExitAnimator.setupStartValues() + drawProperties.timeScale = 0f + } else { + ambientExitAnimator.setupEndValues() + drawProperties.timeScale = 1f + } } } } diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index acae298..d0808f5 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -203,6 +203,34 @@ class WatchFaceConfigStateHolder( BOTTOM_COMPLICATION_ID } + TOP_LEFT_COMPLICATION_ID -> { + TOP_LEFT_COMPLICATION_ID + } + + BOTTOM_LEFT_COMPLICATION_ID -> { + BOTTOM_LEFT_COMPLICATION_ID + } + + TOP_RIGHT_COMPLICATION_ID -> { + TOP_RIGHT_COMPLICATION_ID + } + + BOTTOM_RIGHT_COMPLICATION_ID -> { + BOTTOM_RIGHT_COMPLICATION_ID + } + + LEFT_ICON_COMPLICATION_ID -> { + LEFT_ICON_COMPLICATION_ID + } + + RIGHT_ICON_COMPLICATION_ID -> { + RIGHT_ICON_COMPLICATION_ID + } + + RIGHT_TEXT_COMPLICATION_ID -> { + RIGHT_TEXT_COMPLICATION_ID + } + else -> { launchInProgress = false return diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index d99768c..1970c1b 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -70,7 +70,7 @@ const val HORIZONTAL_COMPLICATION_LEFT_BOUND = 102f / 384f const val HORIZONTAL_COMPLICATION_RIGHT_BOUND = 1f - HORIZONTAL_COMPLICATION_LEFT_BOUND // offset: 18px, height: 51px (canvas 384x384) -const val HORIZONTAL_COMPLICATION_OFFSET = 24f / 384f +const val HORIZONTAL_COMPLICATION_OFFSET = 27f / 384f const val HORIZONTAL_COMPLICATION_HEIGHT = 48f / 384f //const val HORIZONTAL_COMPLICATION_WIDTH = HORIZONTAL_COMPLICATION_RIGHT_BOUND - HORIZONTAL_COMPLICATION_LEFT_BOUND @@ -97,25 +97,25 @@ internal const val LEFT_ICON_COMPLICATION_ID = 108 internal const val RIGHT_ICON_COMPLICATION_ID = 109 internal const val RIGHT_TEXT_COMPLICATION_ID = 110 -private const val TOP_LEFT_COMPLICATION_LEFT_BOUND = 27f / 384f -private const val TOP_LEFT_COMPLICATION_TOP_BOUND = 87f / 384f -private const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 27f / 384f + 78f / 384f -private const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 102f / 384f +private const val TOP_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f +private const val TOP_LEFT_COMPLICATION_TOP_BOUND = 99f / 384f +private const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f +private const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 99f / 384f + 90f / 384f -private const val BOTTOM_LEFT_COMPLICATION_LEFT_BOUND = 27f / 384f +private const val BOTTOM_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f private const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 195f / 384f -private const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 27f / 384f + 78f / 384f -private const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 195f / 384f + 102f / 384f +private const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f +private const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 195f / 384f + 90f / 384f private const val TOP_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f -private const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 87f / 384f -private const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 78f / 384f -private const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 102f / 384f +private const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 99f / 384f +private const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f +private const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 99f / 384f + 90f / 384f private const val BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f private const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 195f / 384f -private const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 78f / 384f -private const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 195f / 384f + 102f / 384f +private const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f +private const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 195f / 384f + 90f / 384f private const val LEFT_ICON_COMPLICATION_LEFT_BOUND = 33f / 384f private const val LEFT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index a4d35b8..d45dcfc 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -76,6 +76,10 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) { if (bounds.isEmpty) return + canvas.drawRect(bounds, Paint().apply { + color = Color.parseColor("#11ffffff") + }) + when (data.type) { ComplicationType.SHORT_TEXT -> { renderShortTextComplication(canvas, bounds, data as ShortTextComplicationData) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt index 2654a93..e6cc08c 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt @@ -47,6 +47,10 @@ class IconComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return + canvas.drawRect(bounds, Paint().apply { + color = Color.parseColor("#11ffffff") + }) + when (data.type) { ComplicationType.MONOCHROMATIC_IMAGE -> { renderMonochromaticImageComplication( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index 1c1cbac..9a567ab 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -16,6 +16,7 @@ package dev.rdnt.m8face.utils import android.content.Context +import android.graphics.drawable.Icon import android.text.format.DateFormat import androidx.wear.watchface.style.UserStyleSchema import androidx.wear.watchface.style.UserStyleSetting @@ -55,13 +56,13 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { resources = context.resources, displayNameResourceId = R.string.layout_style_setting, descriptionResourceId = R.string.layout_style_setting_description, - icon = null, + icon = Icon.createWithResource(context, R.drawable.mauve_style_icon), complicationConfig = listOf( ComplicationSlotsOption( id = UserStyleSetting.Option.Id(LayoutStyle.DEFAULT.id), resources = context.resources, displayNameResourceId = R.string.default_layout_style_name, - icon = null, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( LEFT_COMPLICATION_ID, @@ -86,7 +87,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.FOCUS.id), resources = context.resources, displayNameResourceId = R.string.focus_layout_style_name, - icon = null, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( LEFT_ICON_COMPLICATION_ID, @@ -103,7 +104,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.SPORT.id), resources = context.resources, displayNameResourceId = R.string.sport_layout_style_name, - icon = null, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( TOP_COMPLICATION_ID, @@ -124,7 +125,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.COMPLICATIONS.id), resources = context.resources, displayNameResourceId = R.string.complications_layout_style_name, - icon = null, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( TOP_COMPLICATION_ID, @@ -158,7 +159,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.DEFAULT.id), resources = context.resources, displayNameResourceId = R.string.default_layout_style_name, - icon = null, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( LEFT_COMPLICATION_ID, diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index f0075b5..ccac420 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -77,6 +77,10 @@ class VerticalComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return + canvas.drawRect(bounds, Paint().apply { + color = Color.parseColor("#11ffffff") + }) + when (data.type) { ComplicationType.SHORT_TEXT -> { renderShortTextComplication(canvas, bounds, data as ShortTextComplicationData) @@ -122,11 +126,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { if (isBattery) { val drawable = ContextCompat.getDrawable(context, R.drawable.battery_icon_32)!! icon = drawable.toBitmap( - (32f / 78f * bounds.width()).toInt(), - (32f / 78f * bounds.width()).toInt() + (32f / 72f * bounds.width()).toInt(), + (32f / 72f * bounds.width()).toInt() ) iconBounds = - Rect(0, 0, (32f / 78f * bounds.width()).toInt(), (32f / 78f * bounds.width()).toInt()) + Rect(0, 0, (32f / 72f * bounds.width()).toInt(), (32f / 72f * bounds.width()).toInt()) } else if (data.monochromaticImage != null) { val drawable = data.monochromaticImage!!.image.loadDrawable(context) if (drawable != null) { @@ -149,11 +153,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { } if (text.length <= 3) { - textPaint.textSize = 24F / 78F * bounds.width() + textPaint.textSize = 24F / 72f * bounds.width() } else if (text.length <= 6) { - textPaint.textSize = 16F / 78F * bounds.width() + textPaint.textSize = 16F / 72f * bounds.width() } else { - textPaint.textSize = 12F / 78F * bounds.width() + textPaint.textSize = 12F / 72f * bounds.width() } val textBounds = Rect() @@ -168,11 +172,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { if (title != null) { if (title.length <= 3) { - titlePaint.textSize = 24F / 78F * bounds.width() + titlePaint.textSize = 24F / 72f * bounds.width() } else if (title.length <= 6) { - titlePaint.textSize = 16F / 78F * bounds.width() + titlePaint.textSize = 16F / 72f * bounds.width() } else { - titlePaint.textSize = 12F / 78F * bounds.width() + titlePaint.textSize = 12F / 72f * bounds.width() } titlePaint.getTextBounds(title, 0, title.length, titleBounds) @@ -188,20 +192,20 @@ class VerticalComplication(private val context: Context) : CanvasComplication { iconOffsetY = (height - iconBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - iconOffsetY += 9f / 132f * bounds.height() + iconOffsetY += 6f / 132f * bounds.height() if (isBattery) { iconOffsetY = iconOffsetY.toInt().toFloat() } - textOffsetY += 9f / 132f * bounds.height() + textOffsetY += 6f / 132f * bounds.height() } else if (title != null) { val height = titleBounds.height() + textBounds.height() titleOffsetY = (height - titleBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - titleOffsetY += 9f / 132f * bounds.height() - textOffsetY += 9f / 132f * bounds.height() + titleOffsetY += 6f / 132f * bounds.height() + textOffsetY += 6f / 132f * bounds.height() } if (icon != null) { @@ -252,7 +256,7 @@ class VerticalComplication(private val context: Context) : CanvasComplication { val drawable = data.monochromaticImage.image.loadDrawable(context) ?: return - val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.8f).toInt() + val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.75f).toInt() icon = drawable.toBitmap(size, size) iconBounds = Rect(0, 0, size, size) From e47cdeea27f28020a94b28c8aa9d4afd035d2c9c Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Fri, 19 Jan 2024 22:12:14 +0200 Subject: [PATCH 06/52] Reusable complication button groundwork --- .../m8face/editor/WatchFaceConfigActivity.kt | 767 +++++++++++------- .../rdnt/m8face/utils/ComplicationUtils.kt | 48 +- .../rdnt/m8face/utils/VerticalComplication.kt | 8 +- 3 files changed, 494 insertions(+), 329 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index 60caa00..a9e85f3 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -29,15 +29,43 @@ import android.view.HapticFeedbackConstants.LONG_PRESS import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.core.LinearEasing -import androidx.compose.foundation.* -import androidx.compose.foundation.gestures.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerDefaults +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ripple.LocalRippleTheme import androidx.compose.material.ripple.RippleAlpha import androidx.compose.material.ripple.RippleTheme -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -71,17 +99,24 @@ import androidx.compose.ui.zIndex import androidx.core.graphics.ColorUtils import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope -import androidx.wear.compose.material.* -import androidx.wear.compose.material.ButtonDefaults.outlinedButtonBorder -import androidx.wear.compose.foundation.lazy.ScalingLazyListState +import androidx.wear.compose.foundation.lazy.AutoCenteringParams import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType -import androidx.wear.compose.foundation.lazy.AutoCenteringParams -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.PagerDefaults -import androidx.compose.foundation.pager.PagerState -import androidx.compose.foundation.pager.rememberPagerState +import androidx.wear.compose.foundation.lazy.ScalingLazyListState +import androidx.wear.compose.material.ButtonDefaults.outlinedButtonBorder +import androidx.wear.compose.material.HorizontalPageIndicator +import androidx.wear.compose.material.OutlinedButton +import androidx.wear.compose.material.PageIndicatorState +import androidx.wear.compose.material.PositionIndicator +import androidx.wear.compose.material.PositionIndicatorAlignment +import androidx.wear.compose.material.PositionIndicatorState +import androidx.wear.compose.material.PositionIndicatorVisibility +import androidx.wear.compose.material.Scaffold +import androidx.wear.compose.material.Switch +import androidx.wear.compose.material.Text +import androidx.wear.compose.material.ToggleChip +import androidx.wear.compose.material.ToggleChipDefaults import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.rotaryinput.rotaryWithSnap import com.google.android.horologist.compose.rotaryinput.toRotaryScrollAdapter @@ -92,17 +127,7 @@ import dev.rdnt.m8face.data.watchface.ColorStyle import dev.rdnt.m8face.data.watchface.LayoutStyle import dev.rdnt.m8face.data.watchface.SecondsStyle import dev.rdnt.m8face.theme.WearAppTheme -import dev.rdnt.m8face.utils.BOTTOM_COMPLICATION_ID -import dev.rdnt.m8face.utils.HORIZONTAL_COMPLICATION_HEIGHT -import dev.rdnt.m8face.utils.HORIZONTAL_COMPLICATION_LEFT_BOUND -import dev.rdnt.m8face.utils.HORIZONTAL_COMPLICATION_OFFSET -import dev.rdnt.m8face.utils.HORIZONTAL_COMPLICATION_RIGHT_BOUND -import dev.rdnt.m8face.utils.LEFT_COMPLICATION_ID -import dev.rdnt.m8face.utils.RIGHT_COMPLICATION_ID -import dev.rdnt.m8face.utils.TOP_COMPLICATION_ID -import dev.rdnt.m8face.utils.VERTICAL_COMPLICATION_OFFSET -import dev.rdnt.m8face.utils.VERTICAL_COMPLICATION_TOP_BOUND -import dev.rdnt.m8face.utils.VERTICAL_COMPLICATION_WIDTH +import dev.rdnt.m8face.utils.* import kotlinx.coroutines.launch @Preview() @@ -151,9 +176,9 @@ fun ScrollableColumn( } Scaffold( - Modifier - .onPreRotaryScrollEvent { false } - .fillMaxSize(), + Modifier + .onPreRotaryScrollEvent { false } + .fillMaxSize(), positionIndicator = { PositionIndicator( state = state, @@ -170,69 +195,69 @@ fun ScrollableColumn( Box( modifier = Modifier - .fillMaxSize() - .scrollable( - orientation = Orientation.Vertical, - state = rememberScrollableState { delta -> - val value = position.value - delta - - - if (value < 0f) { - if (position.value != 0f) { - view.performHapticFeedback(LONG_PRESS) - } - - position.value = 0f - } else if (value > maxValue) { - if (position.value != maxValue - 1) { - view.performHapticFeedback(LONG_PRESS) + .fillMaxSize() + .scrollable( + orientation = Orientation.Vertical, + state = rememberScrollableState { delta -> + val value = position.value - delta + + + if (value < 0f) { + if (position.value != 0f) { + view.performHapticFeedback(LONG_PRESS) + } + + position.value = 0f + } else if (value > maxValue) { + if (position.value != maxValue - 1) { + view.performHapticFeedback(LONG_PRESS) + } + + position.value = maxValue - 1 + } else { + position.value = value + val selected = (value / threshold) + .toInt() + .coerceAtMost(items - 1) + if (selected != selectedItem.value) { + selectedItem.value = selected + view.performHapticFeedback(KEYBOARD_TAP) + } + } + + delta } - - position.value = maxValue - 1 - } else { - position.value = value - val selected = (value / threshold) - .toInt() - .coerceAtMost(items - 1) - if (selected != selectedItem.value) { - selectedItem.value = selected - view.performHapticFeedback(KEYBOARD_TAP) + ) + .onRotaryScrollEvent { + val value = position.value + it.verticalScrollPixels + + if (value < 0f) { + if (position.value != 0f) { + view.performHapticFeedback(LONG_PRESS) + } + + position.value = 0f + } else if (value > maxValue) { + if (position.value != maxValue - 1) { + view.performHapticFeedback(LONG_PRESS) + } + + position.value = maxValue - 1 + } else { + position.value = value + val selected = (value / threshold) + .toInt() + .coerceAtMost(items - 1) + if (selected != selectedItem.value) { + selectedItem.value = selected + view.performHapticFeedback(KEYBOARD_TAP) + } } - } - delta - } - ) - .onRotaryScrollEvent { - val value = position.value + it.verticalScrollPixels - - if (value < 0f) { - if (position.value != 0f) { - view.performHapticFeedback(LONG_PRESS) - } - - position.value = 0f - } else if (value > maxValue) { - if (position.value != maxValue - 1) { - view.performHapticFeedback(LONG_PRESS) - } - - position.value = maxValue - 1 - } else { - position.value = value - val selected = (value / threshold) - .toInt() - .coerceAtMost(items - 1) - if (selected != selectedItem.value) { - selectedItem.value = selected - view.performHapticFeedback(KEYBOARD_TAP) - } + true } - - true - } - .focusRequester(focusRequester) - .focusable(), + .focusRequester(focusRequester) + .focusable(), ) } @@ -324,10 +349,10 @@ fun WatchfaceConfigApp( val bigAmbientEnabled = state.userStylesAndPreview.bigAmbient Box( - Modifier - .fillMaxSize() - .zIndex(1f) - .background(Black) + Modifier + .fillMaxSize() + .zIndex(1f) + .background(Black) ) { ConfigScaffold( stateHolder, @@ -348,10 +373,10 @@ fun WatchfaceConfigApp( else -> { Box( - Modifier - .fillMaxSize() - .zIndex(2f) - .background(Black) + Modifier + .fillMaxSize() + .zIndex(2f) + .background(Black) ) { SplashScreen(screenIsRound) } @@ -382,7 +407,7 @@ fun ConfigScaffold( "ConfigScaffold($layoutIndex, $colorIndex, $ambientStyleIndex, $militaryTimeEnabled, $bigAmbientEnabled)" ) - val pagerState = rememberPagerState ( + val pagerState = rememberPagerState( pageCount = { 6 } ) @@ -401,15 +426,15 @@ fun ConfigScaffold( HorizontalPageIndicator( pageIndicatorState = pageIndicatorState, - Modifier - .padding(4.dp) - .zIndex(4f) + Modifier + .padding(4.dp) + .zIndex(4f) ) }, modifier = Modifier - .onPreRotaryScrollEvent { false } - .fillMaxSize() - .zIndex(1f) + .onPreRotaryScrollEvent { false } + .fillMaxSize() + .zIndex(1f) ) { val focusRequester0 = remember { FocusRequester() } @@ -422,8 +447,8 @@ fun ConfigScaffold( flingBehavior = PagerDefaults.flingBehavior(state = pagerState), state = pagerState, modifier = Modifier - .onPreRotaryScrollEvent { pagerState.currentPage != 0 && pagerState.currentPage != 1 && pagerState.currentPage != 2 && pagerState.currentPage != 3 } - .zIndex(3f), // don't ask + .onPreRotaryScrollEvent { pagerState.currentPage != 0 && pagerState.currentPage != 1 && pagerState.currentPage != 2 && pagerState.currentPage != 3 } + .zIndex(3f), // don't ask key = { it } @@ -486,10 +511,10 @@ fun ConfigScaffold( ), contentScale = ContentScale.Crop, modifier = Modifier - .fillMaxSize() - .zIndex(1f) - .clip(TopHalfRectShape) - .scale(1f) + .fillMaxSize() + .zIndex(1f) + .clip(TopHalfRectShape) + .scale(1f) ) Image( painterResource(id = id), @@ -500,10 +525,10 @@ fun ConfigScaffold( ), contentScale = ContentScale.Crop, modifier = Modifier - .fillMaxSize() - .zIndex(1f) - .clip(BottomHalfRectShape) - .scale(1f) + .fillMaxSize() + .zIndex(1f) + .clip(BottomHalfRectShape) + .scale(1f) ) } else { Preview(bitmap) @@ -520,7 +545,7 @@ fun ConfigScaffold( focusRequester1.requestFocus() } else if (pagerState.currentPage == 2) { focusRequester2.requestFocus() - }else if (pagerState.currentPage == 3) { + } else if (pagerState.currentPage == 3) { focusRequester3.requestFocus() } } @@ -540,18 +565,18 @@ fun Overlay( } Box( modifier = Modifier - .fillMaxSize() - .alpha(opacity * 0.75f) - .background( - brush = Brush.verticalGradient( - startY = 192f, - colors = listOf( - Color(0x00000000), - Color(0xFF000000), - ) + .fillMaxSize() + .alpha(opacity * 0.75f) + .background( + brush = Brush.verticalGradient( + startY = 192f, + colors = listOf( + Color(0x00000000), + Color(0xFF000000), + ) + ) ) - ) - .zIndex(2f), + .zIndex(2f), ) } @@ -580,63 +605,63 @@ fun Overlay( ) Box( - Modifier - .zIndex(2f) - .fillMaxSize() - .border(10.dp, Color(ringColor), CircleShape) + Modifier + .zIndex(2f) + .fillMaxSize() + .border(10.dp, Color(ringColor), CircleShape) ) Column( - Modifier - .fillMaxSize() - .padding(10.dp) - .clip(CircleShape) - .zIndex(2f) + Modifier + .fillMaxSize() + .padding(10.dp) + .clip(CircleShape) + .zIndex(2f) ) { Box( - Modifier - .weight(HORIZONTAL_COMPLICATION_OFFSET + HORIZONTAL_COMPLICATION_HEIGHT) - .fillMaxWidth() - .alpha(opacity2 * 0.75f) - .background(Black) + Modifier + .weight(HORIZONTAL_COMPLICATION_OFFSET + HORIZONTAL_COMPLICATION_HEIGHT) + .fillMaxWidth() + .alpha(opacity2 * 0.75f) + .background(Black) ) Row( - Modifier - .weight(1f - HORIZONTAL_COMPLICATION_OFFSET * 2 - HORIZONTAL_COMPLICATION_HEIGHT * 2 + extend) - .fillMaxWidth() + Modifier + .weight(1f - HORIZONTAL_COMPLICATION_OFFSET * 2 - HORIZONTAL_COMPLICATION_HEIGHT * 2 + extend) + .fillMaxWidth() ) { Box( - Modifier - .weight(VERTICAL_COMPLICATION_OFFSET + VERTICAL_COMPLICATION_WIDTH) - .fillMaxHeight() - .alpha(opacity2 * 0.75f) - .background(Black) + Modifier + .weight(VERTICAL_COMPLICATION_OFFSET + VERTICAL_COMPLICATION_WIDTH) + .fillMaxHeight() + .alpha(opacity2 * 0.75f) + .background(Black) ) Box( - Modifier - .weight(1f - VERTICAL_COMPLICATION_OFFSET * 2 - VERTICAL_COMPLICATION_WIDTH * 2 + extend) - .fillMaxHeight() - .alpha(opacity * 0.75f) - .background(Black) + Modifier + .weight(1f - VERTICAL_COMPLICATION_OFFSET * 2 - VERTICAL_COMPLICATION_WIDTH * 2 + extend) + .fillMaxHeight() + .alpha(opacity * 0.75f) + .background(Black) ) Box( - Modifier - .weight(VERTICAL_COMPLICATION_OFFSET + VERTICAL_COMPLICATION_WIDTH) - .fillMaxHeight() - .alpha(opacity2 * 0.75f) - .background(Black) + Modifier + .weight(VERTICAL_COMPLICATION_OFFSET + VERTICAL_COMPLICATION_WIDTH) + .fillMaxHeight() + .alpha(opacity2 * 0.75f) + .background(Black) ) } Box( - Modifier - .weight(HORIZONTAL_COMPLICATION_OFFSET + HORIZONTAL_COMPLICATION_HEIGHT) - .fillMaxWidth() - .alpha(opacity2 * 0.75f) - .background(Black) + Modifier + .weight(HORIZONTAL_COMPLICATION_OFFSET + HORIZONTAL_COMPLICATION_HEIGHT) + .fillMaxWidth() + .alpha(opacity2 * 0.75f) + .background(Black) ) } } @@ -659,9 +684,9 @@ fun Options( ) { ToggleChip( modifier = Modifier - .fillMaxWidth() - .padding(12.dp, 0.dp) - .height((40.dp)), + .fillMaxWidth() + .padding(12.dp, 0.dp) + .height((40.dp)), checked = militaryTime, colors = ToggleChipDefaults.toggleChipColors( checkedStartBackgroundColor = Transparent, @@ -691,9 +716,9 @@ fun Options( ToggleChip( modifier = Modifier - .fillMaxWidth() - .padding(12.dp, 0.dp) - .height((40.dp)), + .fillMaxWidth() + .padding(12.dp, 0.dp) + .height((40.dp)), checked = bigAmbient, colors = ToggleChipDefaults.toggleChipColors( checkedStartBackgroundColor = Transparent, @@ -768,8 +793,8 @@ fun Preview( bitmap = bitmap, contentDescription = "Preview", modifier = Modifier - .fillMaxSize() - .zIndex(1f) + .fillMaxSize() + .zIndex(1f) ) } @@ -963,10 +988,10 @@ fun ColorPicker( alignment = Alignment.CenterVertically ), modifier = Modifier - .fillMaxSize() - .focusable() - .focusRequester(focusRequester) - .rotaryWithSnap(adapter, focusRequester), + .fillMaxSize() + .focusable() + .focusRequester(focusRequester) + .rotaryWithSnap(adapter, focusRequester), state = state, autoCentering = AutoCenteringParams(itemIndex = 0, itemOffset = 0), scalingParams = ScalingLazyColumnDefaults.scalingParams( @@ -995,133 +1020,273 @@ fun ComplicationPicker( ) { Log.d("Editor", "ComplicationPicker()") - Box ( + Box( Modifier .fillMaxSize() ) { - Column( - Modifier - .fillMaxSize() - ) { - Row( - modifier = Modifier - .weight(HORIZONTAL_COMPLICATION_OFFSET, true) - ) {} - Row( - modifier = Modifier - .weight(HORIZONTAL_COMPLICATION_HEIGHT, true) - ) { - Box( - Modifier - .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) - .fillMaxHeight() - ) - OutlinedButton( - onClick = { stateHolder.setComplication(TOP_COMPLICATION_ID) }, - border = outlinedButtonBorder( - Color(0xFF5c6063), - borderWidth = 2.dp - ), - modifier = Modifier - .weight(HORIZONTAL_COMPLICATION_RIGHT_BOUND - HORIZONTAL_COMPLICATION_LEFT_BOUND, true) - .fillMaxHeight() - ) {} - Box( - Modifier - .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) - .fillMaxHeight() - ) - } - Box( - modifier = Modifier - .weight(1f - HORIZONTAL_COMPLICATION_OFFSET * 2 - HORIZONTAL_COMPLICATION_HEIGHT * 2, true) - ) - Row( - modifier = Modifier - .weight(HORIZONTAL_COMPLICATION_HEIGHT, true) - ) { - Box( - Modifier - .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) - .fillMaxHeight() - ) - OutlinedButton( - onClick = { stateHolder.setComplication(BOTTOM_COMPLICATION_ID) }, - border = outlinedButtonBorder( - Color(0xFF5c6063), - borderWidth = 2.dp - ), - modifier = Modifier - .weight(HORIZONTAL_COMPLICATION_RIGHT_BOUND - HORIZONTAL_COMPLICATION_LEFT_BOUND, true) - .fillMaxHeight() - ) {} - Box( - Modifier - .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) - .fillMaxHeight() - ) - } - Row( - modifier = Modifier - .weight(HORIZONTAL_COMPLICATION_OFFSET, true) - ) {} - } +// Column( +// Modifier +// .fillMaxSize() +// ) { +// Row( +// modifier = Modifier +// .weight(HORIZONTAL_COMPLICATION_OFFSET, true) +// ) {} +// Row( +// modifier = Modifier +// .weight(HORIZONTAL_COMPLICATION_HEIGHT, true) +// ) { +// Box( +// Modifier +// .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) +// .fillMaxHeight() +// ) +// OutlinedButton( +// onClick = { stateHolder.setComplication(TOP_COMPLICATION_ID) }, +// border = outlinedButtonBorder( +// Color(0xFF5c6063), +// borderWidth = 2.dp +// ), +// modifier = Modifier +// .weight( +// HORIZONTAL_COMPLICATION_RIGHT_BOUND - HORIZONTAL_COMPLICATION_LEFT_BOUND, +// true +// ) +// .fillMaxHeight() +// ) {} +// Box( +// Modifier +// .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) +// .fillMaxHeight() +// ) +// } +// Box( +// modifier = Modifier +// .weight( +// 1f - HORIZONTAL_COMPLICATION_OFFSET * 2 - HORIZONTAL_COMPLICATION_HEIGHT * 2, +// true +// ) +// ) +// Row( +// modifier = Modifier +// .weight(HORIZONTAL_COMPLICATION_HEIGHT, true) +// ) { +// Box( +// Modifier +// .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) +// .fillMaxHeight() +// ) +// OutlinedButton( +// onClick = { stateHolder.setComplication(BOTTOM_COMPLICATION_ID) }, +// border = outlinedButtonBorder( +// Color(0xFF5c6063), +// borderWidth = 2.dp +// ), +// modifier = Modifier +// .weight( +// HORIZONTAL_COMPLICATION_RIGHT_BOUND - HORIZONTAL_COMPLICATION_LEFT_BOUND, +// true +// ) +// .fillMaxHeight() +// ) {} +// Box( +// Modifier +// .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) +// .fillMaxHeight() +// ) +// } +// Row( +// modifier = Modifier +// .weight(HORIZONTAL_COMPLICATION_OFFSET, true) +// ) {} +// } + + ComplicationButton( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_BOTTOM_BOUND, + ) + + ComplicationButton( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_BOTTOM_BOUND, + ) + +// Column( +// Modifier +// .fillMaxSize() +// ) { +// Box( +// modifier = Modifier +// .weight(VERTICAL_COMPLICATION_TOP_BOUND, true) +// ) +// +// Row( +// modifier = Modifier +// .weight(1f - VERTICAL_COMPLICATION_TOP_BOUND * 2, true) +// ) { +// Box( +// Modifier +// .weight(VERTICAL_COMPLICATION_OFFSET, true) +// .fillMaxHeight() +// ) +// OutlinedButton( +// onClick = { stateHolder.setComplication(LEFT_COMPLICATION_ID) }, +// border = outlinedButtonBorder( +// Color(0xFF5c6063), +// borderWidth = 2.dp +// ), +// modifier = Modifier +// .weight(VERTICAL_COMPLICATION_WIDTH, true) +// .fillMaxHeight(), +// ) {} +// Box( +// Modifier +// .weight(1f - VERTICAL_COMPLICATION_WIDTH * 2 - VERTICAL_COMPLICATION_OFFSET * 2, true) +// .fillMaxHeight() +// ) +// OutlinedButton( +// onClick = { stateHolder.setComplication(RIGHT_COMPLICATION_ID) }, +// border = outlinedButtonBorder( +// Color(0xFF5c6063), +// borderWidth = 2.dp +// ), +// modifier = Modifier +// .weight(VERTICAL_COMPLICATION_WIDTH, true) +// .fillMaxHeight(), +// ) {} +// Box( +// Modifier +// .weight(VERTICAL_COMPLICATION_OFFSET, true) +// .fillMaxHeight() +// ) +// } +// +// Box( +// modifier = Modifier +// .weight(VERTICAL_COMPLICATION_TOP_BOUND, true) +// ) +// } + + ComplicationButton( + TOP_LEFT_COMPLICATION_LEFT_BOUND, + TOP_LEFT_COMPLICATION_TOP_BOUND, + TOP_LEFT_COMPLICATION_RIGHT_BOUND, + TOP_LEFT_COMPLICATION_BOTTOM_BOUND, + ) + + ComplicationButton( + BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, + BOTTOM_LEFT_COMPLICATION_TOP_BOUND, + BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, + BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND, + ) + ComplicationButton( + TOP_RIGHT_COMPLICATION_LEFT_BOUND, + TOP_RIGHT_COMPLICATION_TOP_BOUND, + TOP_RIGHT_COMPLICATION_RIGHT_BOUND, + TOP_RIGHT_COMPLICATION_BOTTOM_BOUND, + ) + + ComplicationButton( + BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, + BOTTOM_RIGHT_COMPLICATION_TOP_BOUND, + BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, + BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND, + ) + +// Row( +// Modifier +// .fillMaxSize() +// ){ +// Box( +// modifier = Modifier +// .fillMaxHeight() +// .weight(TOP_LEFT_COMPLICATION_LEFT_BOUND, true).background(Color.Red) +// ) +// +// Column( +// Modifier +// .fillMaxHeight() +// .weight(TOP_LEFT_COMPLICATION_RIGHT_BOUND-TOP_LEFT_COMPLICATION_LEFT_BOUND, true) +// ) { +// Box( +// modifier = Modifier +// .fillMaxWidth() +// .weight(TOP_LEFT_COMPLICATION_TOP_BOUND, true).background(Color.Red) +// ) +// +// Box( +// modifier = Modifier +// .fillMaxWidth() +// .weight(TOP_LEFT_COMPLICATION_BOTTOM_BOUND-TOP_LEFT_COMPLICATION_TOP_BOUND, true).background(Color.Blue) +// ) +// +// Box( +// modifier = Modifier +// .fillMaxWidth() +// .weight(1f - TOP_LEFT_COMPLICATION_BOTTOM_BOUND , true).background(Color.Red) +// ) +// +// } +// +// Box( +// modifier = Modifier +// .fillMaxHeight() +// .weight(1f-TOP_LEFT_COMPLICATION_RIGHT_BOUND, true).background(Color.Red) +// ) +// +// +// } + + + } +} + +@Composable +fun ComplicationButton(left: Float, top: Float, right: Float, bottom: Float) { + var left = left - 0.012f + var right = right + 0.012f + var top = top - 0.012f + var bottom = bottom + 0.012f + + Log.d("Editor", "ComplicationButton(${left}, ${top}, ${right}, ${bottom})") + + Row(Modifier.fillMaxSize()) { + Box(Modifier.weight(left, true)) Column( - Modifier - .fillMaxSize() + Modifier + .fillMaxSize() + .weight(right - left, true) ) { - Box( - modifier = Modifier - .weight(VERTICAL_COMPLICATION_TOP_BOUND, true) - ) + Box(Modifier.weight(top, true)) - Row( - modifier = Modifier - .weight(1f - VERTICAL_COMPLICATION_TOP_BOUND * 2, true) - ) { - Box( - Modifier - .weight(VERTICAL_COMPLICATION_OFFSET, true) - .fillMaxHeight() - ) - OutlinedButton( - onClick = { stateHolder.setComplication(LEFT_COMPLICATION_ID) }, - border = outlinedButtonBorder( - Color(0xFF5c6063), - borderWidth = 2.dp - ), - modifier = Modifier - .weight(VERTICAL_COMPLICATION_WIDTH, true) - .fillMaxHeight(), - ) {} - Box( - Modifier - .weight(1f - VERTICAL_COMPLICATION_WIDTH * 2 - VERTICAL_COMPLICATION_OFFSET * 2, true) - .fillMaxHeight() - ) - OutlinedButton( - onClick = { stateHolder.setComplication(RIGHT_COMPLICATION_ID) }, + OutlinedButton( + onClick = {Log.d("@@@", "CLICK") }, border = outlinedButtonBorder( Color(0xFF5c6063), borderWidth = 2.dp ), +// shape = RoundedCornerShape(16.dp), modifier = Modifier - .weight(VERTICAL_COMPLICATION_WIDTH, true) - .fillMaxHeight(), + .weight(bottom - top, true) + .fillMaxSize(), ) {} - Box( - Modifier - .weight(VERTICAL_COMPLICATION_OFFSET, true) - .fillMaxHeight() - ) - } - Box( - modifier = Modifier - .weight(VERTICAL_COMPLICATION_TOP_BOUND, true) - ) +// Box( +// Modifier +// .fillMaxWidth() +// .weight(bottom - top, true) +// .alpha(.3f) +// .background(Color.Blue) +// ) + Box(Modifier.weight(1f - bottom, true)) } + Box(Modifier.weight(1f - right, true)) } } @@ -1140,11 +1305,11 @@ fun ColorName(name: String) { text = name, textAlign = TextAlign.Center, modifier = Modifier - .weight(4f) - .height(20.dp) - .clip(RoundedCornerShape(10.dp)) - .background(Color(0xFF202124)) - .padding(12.dp, 1.dp), + .weight(4f) + .height(20.dp) + .clip(RoundedCornerShape(10.dp)) + .background(Color(0xFF202124)) + .padding(12.dp, 1.dp), fontSize = 12.sp, fontWeight = Medium, fontFamily = Default, @@ -1179,10 +1344,10 @@ fun Label(label: String) { text = label, textAlign = TextAlign.Center, modifier = Modifier - .weight(4f) - .clip(RoundedCornerShape(10.dp)) - .background(Color(0xFF000000)) - .padding(12.dp, 1.dp), + .weight(4f) + .clip(RoundedCornerShape(10.dp)) + .background(Color(0xFF000000)) + .padding(12.dp, 1.dp), fontSize = 12.sp, fontWeight = Medium, fontFamily = Default, @@ -1200,10 +1365,10 @@ fun ColorItem() { Box( modifier = Modifier - .padding(0.dp, 0.dp) - .height(48.dp) - .width(0.dp) - .alpha(0.0F) + .padding(0.dp, 0.dp) + .height(48.dp) + .width(0.dp) + .alpha(0.0F) ) } @@ -1213,12 +1378,12 @@ fun DebugColorItem(colorId: Int?) { Box( modifier = Modifier - .padding(0.dp, 0.dp) - .height(48.dp) - .fillMaxWidth() - .alpha(0.0F) + .padding(0.dp, 0.dp) + .height(48.dp) + .fillMaxWidth() + .alpha(0.0F) // .alpha(0.2F) - .background(colorId?.let { colorResource(colorId) } ?: Black) + .background(colorId?.let { colorResource(colorId) } ?: Black) ) } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index 1970c1b..b52e18c 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -66,7 +66,7 @@ private const val RIGHT_COMPLICATION_LEFT_BOUND = private const val RIGHT_COMPLICATION_RIGHT_BOUND = 1f - VERTICAL_COMPLICATION_OFFSET // Both left and right complications use the same top and bottom bounds. -const val HORIZONTAL_COMPLICATION_LEFT_BOUND = 102f / 384f +const val HORIZONTAL_COMPLICATION_LEFT_BOUND = 99f / 384f const val HORIZONTAL_COMPLICATION_RIGHT_BOUND = 1f - HORIZONTAL_COMPLICATION_LEFT_BOUND // offset: 18px, height: 51px (canvas 384x384) @@ -75,13 +75,13 @@ const val HORIZONTAL_COMPLICATION_HEIGHT = 48f / 384f //const val HORIZONTAL_COMPLICATION_WIDTH = HORIZONTAL_COMPLICATION_RIGHT_BOUND - HORIZONTAL_COMPLICATION_LEFT_BOUND -private const val TOP_COMPLICATION_TOP_BOUND = HORIZONTAL_COMPLICATION_OFFSET -private const val TOP_COMPLICATION_BOTTOM_BOUND = + const val TOP_COMPLICATION_TOP_BOUND = HORIZONTAL_COMPLICATION_OFFSET + const val TOP_COMPLICATION_BOTTOM_BOUND = HORIZONTAL_COMPLICATION_OFFSET + HORIZONTAL_COMPLICATION_HEIGHT -private const val BOTTOM_COMPLICATION_TOP_BOUND = + const val BOTTOM_COMPLICATION_TOP_BOUND = 1f - HORIZONTAL_COMPLICATION_OFFSET - HORIZONTAL_COMPLICATION_HEIGHT -private const val BOTTOM_COMPLICATION_BOTTOM_BOUND = 1f - HORIZONTAL_COMPLICATION_OFFSET + const val BOTTOM_COMPLICATION_BOTTOM_BOUND = 1f - HORIZONTAL_COMPLICATION_OFFSET // Unique IDs for each complication. The settings activity that supports allowing users // to select their complication data provider requires numbers to be >= 0. @@ -97,25 +97,25 @@ internal const val LEFT_ICON_COMPLICATION_ID = 108 internal const val RIGHT_ICON_COMPLICATION_ID = 109 internal const val RIGHT_TEXT_COMPLICATION_ID = 110 -private const val TOP_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f -private const val TOP_LEFT_COMPLICATION_TOP_BOUND = 99f / 384f -private const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f -private const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 99f / 384f + 90f / 384f - -private const val BOTTOM_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f -private const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 195f / 384f -private const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f -private const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 195f / 384f + 90f / 384f - -private const val TOP_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f -private const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 99f / 384f -private const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f -private const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 99f / 384f + 90f / 384f - -private const val BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f -private const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 195f / 384f -private const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f -private const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 195f / 384f + 90f / 384f +const val TOP_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f +const val TOP_LEFT_COMPLICATION_TOP_BOUND = 87f / 384f +const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f +const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 96f / 384f + +const val BOTTOM_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f +const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 201f / 384f +const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f +const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 201f / 384f + 96f / 384f + +const val TOP_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f +const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 87f / 384f +const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f +const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 96f / 384f + +const val BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f +const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 201f / 384f +const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f +const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 201f / 384f + 96f / 384f private const val LEFT_ICON_COMPLICATION_LEFT_BOUND = 33f / 384f private const val LEFT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index ccac420..33c3e2c 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -192,20 +192,20 @@ class VerticalComplication(private val context: Context) : CanvasComplication { iconOffsetY = (height - iconBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - iconOffsetY += 6f / 132f * bounds.height() + iconOffsetY += 9f / 132f * bounds.height() if (isBattery) { iconOffsetY = iconOffsetY.toInt().toFloat() } - textOffsetY += 6f / 132f * bounds.height() + textOffsetY += 3f / 132f * bounds.height() } else if (title != null) { val height = titleBounds.height() + textBounds.height() titleOffsetY = (height - titleBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - titleOffsetY += 6f / 132f * bounds.height() - textOffsetY += 6f / 132f * bounds.height() + titleOffsetY += 9f / 132f * bounds.height() + textOffsetY += 3f / 132f * bounds.height() } if (icon != null) { From e0aaa7c0f00c5cb947f58341224e5140aa5fcbec Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Fri, 19 Jan 2024 22:26:19 +0200 Subject: [PATCH 07/52] Re-usable complication button --- .../m8face/editor/WatchFaceConfigActivity.kt | 92 +++++++++++++------ .../editor/WatchFaceConfigStateHolder.kt | 5 +- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index a9e85f3..a702625 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -22,6 +22,8 @@ package dev.rdnt.m8face.editor +import android.graphics.Rect +import android.graphics.RectF import android.os.Bundle import android.util.Log import android.view.HapticFeedbackConstants.KEYBOARD_TAP @@ -1102,17 +1104,25 @@ fun ComplicationPicker( // } ComplicationButton( - HORIZONTAL_COMPLICATION_LEFT_BOUND, - TOP_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, - TOP_COMPLICATION_BOTTOM_BOUND, + stateHolder, + TOP_COMPLICATION_ID, + RectF( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_BOTTOM_BOUND, + ), ) ComplicationButton( - HORIZONTAL_COMPLICATION_LEFT_BOUND, - BOTTOM_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, - BOTTOM_COMPLICATION_BOTTOM_BOUND, + stateHolder, + BOTTOM_COMPLICATION_ID, + RectF( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_BOTTOM_BOUND, + ), ) // Column( @@ -1172,31 +1182,48 @@ fun ComplicationPicker( // } ComplicationButton( - TOP_LEFT_COMPLICATION_LEFT_BOUND, - TOP_LEFT_COMPLICATION_TOP_BOUND, - TOP_LEFT_COMPLICATION_RIGHT_BOUND, - TOP_LEFT_COMPLICATION_BOTTOM_BOUND, + stateHolder, + TOP_LEFT_COMPLICATION_ID, + RectF( + TOP_LEFT_COMPLICATION_LEFT_BOUND, + TOP_LEFT_COMPLICATION_TOP_BOUND, + TOP_LEFT_COMPLICATION_RIGHT_BOUND, + TOP_LEFT_COMPLICATION_BOTTOM_BOUND, + ), ) + ComplicationButton( - BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, - BOTTOM_LEFT_COMPLICATION_TOP_BOUND, - BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, - BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND, + stateHolder, + BOTTOM_LEFT_COMPLICATION_ID, + RectF( + BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, + BOTTOM_LEFT_COMPLICATION_TOP_BOUND, + BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, + BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND, + ), ) ComplicationButton( - TOP_RIGHT_COMPLICATION_LEFT_BOUND, - TOP_RIGHT_COMPLICATION_TOP_BOUND, - TOP_RIGHT_COMPLICATION_RIGHT_BOUND, - TOP_RIGHT_COMPLICATION_BOTTOM_BOUND, + stateHolder, + TOP_RIGHT_COMPLICATION_ID, + RectF( + TOP_RIGHT_COMPLICATION_LEFT_BOUND, + TOP_RIGHT_COMPLICATION_TOP_BOUND, + TOP_RIGHT_COMPLICATION_RIGHT_BOUND, + TOP_RIGHT_COMPLICATION_BOTTOM_BOUND, + ), ) ComplicationButton( - BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, - BOTTOM_RIGHT_COMPLICATION_TOP_BOUND, - BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, - BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND, + stateHolder, + BOTTOM_RIGHT_COMPLICATION_ID, + RectF( + BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, + BOTTOM_RIGHT_COMPLICATION_TOP_BOUND, + BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, + BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND, + ), ) // Row( @@ -1248,11 +1275,16 @@ fun ComplicationPicker( } @Composable -fun ComplicationButton(left: Float, top: Float, right: Float, bottom: Float) { - var left = left - 0.012f - var right = right + 0.012f - var top = top - 0.012f - var bottom = bottom + 0.012f +fun ComplicationButton( + stateHolder: WatchFaceConfigStateHolder, + id: Int, + bounds: RectF, +// left: Float, top: Float, right: Float, bottom: Float +) { + var left = bounds.left - 0.012f + var right = bounds.right + 0.012f + var top = bounds.top - 0.012f + var bottom = bounds.bottom + 0.012f Log.d("Editor", "ComplicationButton(${left}, ${top}, ${right}, ${bottom})") @@ -1266,7 +1298,7 @@ fun ComplicationButton(left: Float, top: Float, right: Float, bottom: Float) { Box(Modifier.weight(top, true)) OutlinedButton( - onClick = {Log.d("@@@", "CLICK") }, + onClick = { stateHolder.setComplication(id) }, border = outlinedButtonBorder( Color(0xFF5c6063), borderWidth = 2.dp diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index d0808f5..55834ac 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -22,7 +22,6 @@ import androidx.wear.watchface.DrawMode import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.editor.EditorSession -import androidx.wear.watchface.style.ExperimentalHierarchicalStyle import androidx.wear.watchface.style.UserStyle import androidx.wear.watchface.style.UserStyleSchema import androidx.wear.watchface.style.UserStyleSetting @@ -180,13 +179,13 @@ class WatchFaceConfigStateHolder( ) } - fun setComplication(complicationLocation: Int) { + fun setComplication(complicationId: Int) { if (launchInProgress) { return } launchInProgress = true - val complicationSlotId = when (complicationLocation) { + val complicationSlotId = when (complicationId) { LEFT_COMPLICATION_ID -> { LEFT_COMPLICATION_ID } From 36c98245593e9a6fcbf2b1166f6e9a63e303eb1d Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Fri, 19 Jan 2024 23:05:41 +0200 Subject: [PATCH 08/52] Dynamic complication editor based on selected layout --- .../m8face/editor/WatchFaceConfigActivity.kt | 413 ++++++++---------- .../rdnt/m8face/utils/ComplicationUtils.kt | 53 ++- .../rdnt/m8face/utils/VerticalComplication.kt | 4 +- 3 files changed, 202 insertions(+), 268 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index a702625..60c0fa6 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -22,7 +22,6 @@ package dev.rdnt.m8face.editor -import android.graphics.Rect import android.graphics.RectF import android.os.Bundle import android.util.Log @@ -460,7 +459,7 @@ fun ConfigScaffold( 1 -> ColorStyleSelect(focusRequester1, stateHolder, colorStyles, colorIndex) 2 -> SecondsStyleSelect(focusRequester2, stateHolder, secondsStyles, secondsStyleIndex) 3 -> AmbientStyleSelect(focusRequester3, stateHolder, ambientStyles, ambientStyleIndex) - 4 -> ComplicationPicker(stateHolder) + 4 -> ComplicationPicker(stateHolder, layoutIndex) 5 -> Options(stateHolder, militaryTimeEnabled, bigAmbientEnabled) } } @@ -1019,6 +1018,7 @@ fun ColorPicker( @Composable fun ComplicationPicker( stateHolder: WatchFaceConfigStateHolder, + layoutIndex: Int, ) { Log.d("Editor", "ComplicationPicker()") @@ -1026,249 +1026,178 @@ fun ComplicationPicker( Modifier .fillMaxSize() ) { -// Column( -// Modifier -// .fillMaxSize() -// ) { -// Row( -// modifier = Modifier -// .weight(HORIZONTAL_COMPLICATION_OFFSET, true) -// ) {} -// Row( -// modifier = Modifier -// .weight(HORIZONTAL_COMPLICATION_HEIGHT, true) -// ) { -// Box( -// Modifier -// .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) -// .fillMaxHeight() -// ) -// OutlinedButton( -// onClick = { stateHolder.setComplication(TOP_COMPLICATION_ID) }, -// border = outlinedButtonBorder( -// Color(0xFF5c6063), -// borderWidth = 2.dp -// ), -// modifier = Modifier -// .weight( -// HORIZONTAL_COMPLICATION_RIGHT_BOUND - HORIZONTAL_COMPLICATION_LEFT_BOUND, -// true -// ) -// .fillMaxHeight() -// ) {} -// Box( -// Modifier -// .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) -// .fillMaxHeight() -// ) -// } -// Box( -// modifier = Modifier -// .weight( -// 1f - HORIZONTAL_COMPLICATION_OFFSET * 2 - HORIZONTAL_COMPLICATION_HEIGHT * 2, -// true -// ) -// ) -// Row( -// modifier = Modifier -// .weight(HORIZONTAL_COMPLICATION_HEIGHT, true) -// ) { -// Box( -// Modifier -// .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) -// .fillMaxHeight() -// ) -// OutlinedButton( -// onClick = { stateHolder.setComplication(BOTTOM_COMPLICATION_ID) }, -// border = outlinedButtonBorder( -// Color(0xFF5c6063), -// borderWidth = 2.dp -// ), -// modifier = Modifier -// .weight( -// HORIZONTAL_COMPLICATION_RIGHT_BOUND - HORIZONTAL_COMPLICATION_LEFT_BOUND, -// true -// ) -// .fillMaxHeight() -// ) {} -// Box( -// Modifier -// .weight(HORIZONTAL_COMPLICATION_LEFT_BOUND, true) -// .fillMaxHeight() -// ) -// } -// Row( -// modifier = Modifier -// .weight(HORIZONTAL_COMPLICATION_OFFSET, true) -// ) {} -// } - - ComplicationButton( - stateHolder, - TOP_COMPLICATION_ID, - RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, - TOP_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, - TOP_COMPLICATION_BOTTOM_BOUND, - ), - ) - ComplicationButton( - stateHolder, - BOTTOM_COMPLICATION_ID, - RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, - BOTTOM_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, - BOTTOM_COMPLICATION_BOTTOM_BOUND, - ), - ) + when (layoutIndex) { + 0 -> { + ComplicationButton( + stateHolder, + TOP_COMPLICATION_ID, + RectF( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_BOTTOM_BOUND, + ), + ) -// Column( -// Modifier -// .fillMaxSize() -// ) { -// Box( -// modifier = Modifier -// .weight(VERTICAL_COMPLICATION_TOP_BOUND, true) -// ) -// -// Row( -// modifier = Modifier -// .weight(1f - VERTICAL_COMPLICATION_TOP_BOUND * 2, true) -// ) { -// Box( -// Modifier -// .weight(VERTICAL_COMPLICATION_OFFSET, true) -// .fillMaxHeight() -// ) -// OutlinedButton( -// onClick = { stateHolder.setComplication(LEFT_COMPLICATION_ID) }, -// border = outlinedButtonBorder( -// Color(0xFF5c6063), -// borderWidth = 2.dp -// ), -// modifier = Modifier -// .weight(VERTICAL_COMPLICATION_WIDTH, true) -// .fillMaxHeight(), -// ) {} -// Box( -// Modifier -// .weight(1f - VERTICAL_COMPLICATION_WIDTH * 2 - VERTICAL_COMPLICATION_OFFSET * 2, true) -// .fillMaxHeight() -// ) -// OutlinedButton( -// onClick = { stateHolder.setComplication(RIGHT_COMPLICATION_ID) }, -// border = outlinedButtonBorder( -// Color(0xFF5c6063), -// borderWidth = 2.dp -// ), -// modifier = Modifier -// .weight(VERTICAL_COMPLICATION_WIDTH, true) -// .fillMaxHeight(), -// ) {} -// Box( -// Modifier -// .weight(VERTICAL_COMPLICATION_OFFSET, true) -// .fillMaxHeight() -// ) -// } -// -// Box( -// modifier = Modifier -// .weight(VERTICAL_COMPLICATION_TOP_BOUND, true) -// ) -// } - - ComplicationButton( - stateHolder, - TOP_LEFT_COMPLICATION_ID, - RectF( - TOP_LEFT_COMPLICATION_LEFT_BOUND, - TOP_LEFT_COMPLICATION_TOP_BOUND, - TOP_LEFT_COMPLICATION_RIGHT_BOUND, - TOP_LEFT_COMPLICATION_BOTTOM_BOUND, - ), - ) + ComplicationButton( + stateHolder, + BOTTOM_COMPLICATION_ID, + RectF( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_BOTTOM_BOUND, + ), + ) + ComplicationButton( + stateHolder, + LEFT_COMPLICATION_ID, + RectF( + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND, + ), + ) - ComplicationButton( - stateHolder, - BOTTOM_LEFT_COMPLICATION_ID, - RectF( - BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, - BOTTOM_LEFT_COMPLICATION_TOP_BOUND, - BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, - BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND, - ), - ) + ComplicationButton( + stateHolder, + RIGHT_COMPLICATION_ID, + RectF( + RIGHT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + RIGHT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND, + ), + ) + } + 1 -> { + ComplicationButton( + stateHolder, + LEFT_ICON_COMPLICATION_ID, + RectF( + LEFT_ICON_COMPLICATION_LEFT_BOUND, + LEFT_ICON_COMPLICATION_TOP_BOUND, + LEFT_ICON_COMPLICATION_RIGHT_BOUND, + LEFT_ICON_COMPLICATION_BOTTOM_BOUND, + ), + ) - ComplicationButton( - stateHolder, - TOP_RIGHT_COMPLICATION_ID, - RectF( - TOP_RIGHT_COMPLICATION_LEFT_BOUND, - TOP_RIGHT_COMPLICATION_TOP_BOUND, - TOP_RIGHT_COMPLICATION_RIGHT_BOUND, - TOP_RIGHT_COMPLICATION_BOTTOM_BOUND, - ), - ) + ComplicationButton( + stateHolder, + RIGHT_ICON_COMPLICATION_ID, + RectF( + RIGHT_ICON_COMPLICATION_LEFT_BOUND, + RIGHT_ICON_COMPLICATION_TOP_BOUND, + RIGHT_ICON_COMPLICATION_RIGHT_BOUND, + RIGHT_ICON_COMPLICATION_BOTTOM_BOUND, + ), + ) + } + 2 -> { + ComplicationButton( + stateHolder, + TOP_COMPLICATION_ID, + RectF( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_BOTTOM_BOUND, + ), + ) - ComplicationButton( - stateHolder, - BOTTOM_RIGHT_COMPLICATION_ID, - RectF( - BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, - BOTTOM_RIGHT_COMPLICATION_TOP_BOUND, - BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, - BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND, - ), - ) + ComplicationButton( + stateHolder, + BOTTOM_COMPLICATION_ID, + RectF( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_BOTTOM_BOUND, + ), + ) -// Row( -// Modifier -// .fillMaxSize() -// ){ -// Box( -// modifier = Modifier -// .fillMaxHeight() -// .weight(TOP_LEFT_COMPLICATION_LEFT_BOUND, true).background(Color.Red) -// ) -// -// Column( -// Modifier -// .fillMaxHeight() -// .weight(TOP_LEFT_COMPLICATION_RIGHT_BOUND-TOP_LEFT_COMPLICATION_LEFT_BOUND, true) -// ) { -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .weight(TOP_LEFT_COMPLICATION_TOP_BOUND, true).background(Color.Red) -// ) -// -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .weight(TOP_LEFT_COMPLICATION_BOTTOM_BOUND-TOP_LEFT_COMPLICATION_TOP_BOUND, true).background(Color.Blue) -// ) -// -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .weight(1f - TOP_LEFT_COMPLICATION_BOTTOM_BOUND , true).background(Color.Red) -// ) -// -// } -// -// Box( -// modifier = Modifier -// .fillMaxHeight() -// .weight(1f-TOP_LEFT_COMPLICATION_RIGHT_BOUND, true).background(Color.Red) -// ) -// -// -// } + ComplicationButton( + stateHolder, + RIGHT_TEXT_COMPLICATION_ID, + RectF( + RIGHT_TEXT_COMPLICATION_LEFT_BOUND, + RIGHT_TEXT_COMPLICATION_TOP_BOUND, + RIGHT_TEXT_COMPLICATION_RIGHT_BOUND, + RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND, + ), + ) + } + 3-> { + ComplicationButton( + stateHolder, + TOP_COMPLICATION_ID, + RectF( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_BOTTOM_BOUND, + ), + ) + + ComplicationButton( + stateHolder, + BOTTOM_COMPLICATION_ID, + RectF( + HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_TOP_BOUND, + HORIZONTAL_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_BOTTOM_BOUND, + ), + ) + + ComplicationButton( + stateHolder, + TOP_LEFT_COMPLICATION_ID, + RectF( + TOP_LEFT_COMPLICATION_LEFT_BOUND, + TOP_LEFT_COMPLICATION_TOP_BOUND, + TOP_LEFT_COMPLICATION_RIGHT_BOUND, + TOP_LEFT_COMPLICATION_BOTTOM_BOUND, + ), + ) + + ComplicationButton( + stateHolder, + BOTTOM_LEFT_COMPLICATION_ID, + RectF( + BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, + BOTTOM_LEFT_COMPLICATION_TOP_BOUND, + BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, + BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND, + ), + ) + + ComplicationButton( + stateHolder, + TOP_RIGHT_COMPLICATION_ID, + RectF( + TOP_RIGHT_COMPLICATION_LEFT_BOUND, + TOP_RIGHT_COMPLICATION_TOP_BOUND, + TOP_RIGHT_COMPLICATION_RIGHT_BOUND, + TOP_RIGHT_COMPLICATION_BOTTOM_BOUND, + ), + ) + + ComplicationButton( + stateHolder, + BOTTOM_RIGHT_COMPLICATION_ID, + RectF( + BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, + BOTTOM_RIGHT_COMPLICATION_TOP_BOUND, + BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, + BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND, + ), + ) + } + } } @@ -1281,10 +1210,10 @@ fun ComplicationButton( bounds: RectF, // left: Float, top: Float, right: Float, bottom: Float ) { - var left = bounds.left - 0.012f - var right = bounds.right + 0.012f - var top = bounds.top - 0.012f - var bottom = bounds.bottom + 0.012f + var left= bounds.left - 0.015625f + var right= bounds.right + 0.015625f + var top= bounds.top - 0.015625f + var bottom= bounds.bottom + 0.015625f Log.d("Editor", "ComplicationButton(${left}, ${top}, ${right}, ${bottom})") diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index b52e18c..c89d215 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -57,13 +57,13 @@ const val VERTICAL_COMPLICATION_WIDTH = 78f / 384f //const val VERTICAL_COMPLICATION_HEIGHT = VERTICAL_COMPLICATION_BOTTOM_BOUND - VERTICAL_COMPLICATION_TOP_BOUND // 0.03125 -private const val LEFT_COMPLICATION_LEFT_BOUND = VERTICAL_COMPLICATION_OFFSET -private const val LEFT_COMPLICATION_RIGHT_BOUND = + const val LEFT_COMPLICATION_LEFT_BOUND = VERTICAL_COMPLICATION_OFFSET + const val LEFT_COMPLICATION_RIGHT_BOUND = VERTICAL_COMPLICATION_OFFSET + VERTICAL_COMPLICATION_WIDTH -private const val RIGHT_COMPLICATION_LEFT_BOUND = + const val RIGHT_COMPLICATION_LEFT_BOUND = 1f - VERTICAL_COMPLICATION_OFFSET - VERTICAL_COMPLICATION_WIDTH -private const val RIGHT_COMPLICATION_RIGHT_BOUND = 1f - VERTICAL_COMPLICATION_OFFSET + const val RIGHT_COMPLICATION_RIGHT_BOUND = 1f - VERTICAL_COMPLICATION_OFFSET // Both left and right complications use the same top and bottom bounds. const val HORIZONTAL_COMPLICATION_LEFT_BOUND = 99f / 384f @@ -98,34 +98,39 @@ internal const val RIGHT_ICON_COMPLICATION_ID = 109 internal const val RIGHT_TEXT_COMPLICATION_ID = 110 const val TOP_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f -const val TOP_LEFT_COMPLICATION_TOP_BOUND = 87f / 384f +const val TOP_LEFT_COMPLICATION_TOP_BOUND = 84f / 384f const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f -const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 96f / 384f +const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 84f / 384f + 96f / 384f const val BOTTOM_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f -const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 201f / 384f +const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 204f / 384f const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f -const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 201f / 384f + 96f / 384f +const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 204f / 384f + 96f / 384f const val TOP_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f -const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 87f / 384f +const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 84f / 384f const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f -const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 96f / 384f +const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 84f / 384f + 96f / 384f const val BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f -const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 201f / 384f +const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 204f / 384f const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f -const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 201f / 384f + 96f / 384f +const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 204f / 384f + 96f / 384f -private const val LEFT_ICON_COMPLICATION_LEFT_BOUND = 33f / 384f -private const val LEFT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f -private const val LEFT_ICON_COMPLICATION_RIGHT_BOUND = 33f / 384f + 54f / 384f -private const val LEFT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f +const val LEFT_ICON_COMPLICATION_LEFT_BOUND = 33f / 384f +const val LEFT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f +const val LEFT_ICON_COMPLICATION_RIGHT_BOUND = 33f / 384f + 54f / 384f +const val LEFT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f -private const val RIGHT_ICON_COMPLICATION_LEFT_BOUND = 297f / 384f -private const val RIGHT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f -private const val RIGHT_ICON_COMPLICATION_RIGHT_BOUND = 297f / 384f + 54f / 384f -private const val RIGHT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f +const val RIGHT_ICON_COMPLICATION_LEFT_BOUND = 297f / 384f +const val RIGHT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f +const val RIGHT_ICON_COMPLICATION_RIGHT_BOUND = 297f / 384f + 54f / 384f +const val RIGHT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f + +const val RIGHT_TEXT_COMPLICATION_LEFT_BOUND = 248f / 384f +const val RIGHT_TEXT_COMPLICATION_TOP_BOUND = 248f / 384f +const val RIGHT_TEXT_COMPLICATION_RIGHT_BOUND = 248f / 384f + 66f / 384f +const val RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND = 248f / 384f + 15f / 384f /** * Represents the unique id associated with a complication and the complication types it supports. @@ -410,10 +415,10 @@ fun createComplicationSlotManager( ), bounds = ComplicationSlotBounds( RectF( - LEFT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, - LEFT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + RIGHT_TEXT_COMPLICATION_LEFT_BOUND, + RIGHT_TEXT_COMPLICATION_TOP_BOUND, + RIGHT_TEXT_COMPLICATION_RIGHT_BOUND, + RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND ) ) ).setNameResourceId(R.string.right_text_complication_name) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index 33c3e2c..a67f3f0 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -204,8 +204,8 @@ class VerticalComplication(private val context: Context) : CanvasComplication { titleOffsetY = (height - titleBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - titleOffsetY += 9f / 132f * bounds.height() - textOffsetY += 3f / 132f * bounds.height() + titleOffsetY += 6f / 132f * bounds.height() + textOffsetY += 6f / 132f * bounds.height() } if (icon != null) { From f065610ad56d5ffce5b42e4942f73334e7362d80 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 24 Jan 2024 20:39:54 +0200 Subject: [PATCH 09/52] Groundwork on refined canvas renderer --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 177 +++- .../m8face/data/watchface/AmbientStyle.kt | 9 +- .../rdnt/m8face/data/watchface/LayoutStyle.kt | 38 +- .../m8face/data/watchface/WatchFaceData.kt | 2 +- .../m8face/editor/WatchFaceConfigActivity.kt | 275 ++++-- .../editor/WatchFaceConfigStateHolder.kt | 32 +- .../rdnt/m8face/utils/ComplicationUtils.kt | 820 +++++++++++++----- .../m8face/utils/HorizontalComplication.kt | 18 +- .../dev/rdnt/m8face/utils/IconComplication.kt | 5 +- .../m8face/utils/InvisibleComplication.kt | 57 ++ .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 345 +++++--- .../rdnt/m8face/utils/VerticalComplication.kt | 3 +- .../bold_outline_style_icon.png | Bin 3492 -> 3493 bytes app/src/main/res/values/strings.xml | 16 +- 14 files changed, 1339 insertions(+), 458 deletions(-) create mode 100644 app/src/main/java/dev/rdnt/m8face/utils/InvisibleComplication.kt diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index c892ac1..b24d2c0 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -50,6 +50,7 @@ import dev.rdnt.m8face.utils.BIG_AMBIENT_SETTING import dev.rdnt.m8face.utils.COLOR_STYLE_SETTING import dev.rdnt.m8face.utils.HorizontalComplication import dev.rdnt.m8face.utils.IconComplication +import dev.rdnt.m8face.utils.InvisibleComplication import dev.rdnt.m8face.utils.LAYOUT_STYLE_SETTING import dev.rdnt.m8face.utils.MILITARY_TIME_SETTING import dev.rdnt.m8face.utils.SECONDS_STYLE_SETTING @@ -104,6 +105,7 @@ class WatchCanvasRenderer( secondsStyle = SecondsStyle.NONE, militaryTime = true, bigAmbient = false, + layoutStyle = LayoutStyle.INFO1, ) // Converts resource ids into Colors and ComplicationDrawable. @@ -133,7 +135,8 @@ class WatchCanvasRenderer( private val hourPaint = Paint().apply { isAntiAlias = true // make sure text is not anti-aliased even with this on typeface = context.resources.getFont(R.font.m8stealth57) - textSize = 112f / 14f * 14f // TODO: 98f/112f +// textSize = 112f / 14f * 14f // TODO: 98f/112f + textSize = 112f / 14f color = watchFaceColors.primaryColor } @@ -170,7 +173,8 @@ class WatchCanvasRenderer( private val minutePaint = Paint().apply { isAntiAlias = true // make sure text is not anti-aliased even with this on typeface = context.resources.getFont(R.font.m8stealth57) - textSize = 112f / 14f * 14f // TODO: 98f/112F +// textSize = 112f / 14f * 14f // TODO: 98f/112F + textSize = 112f / 14f color = watchFaceColors.secondaryColor } @@ -530,6 +534,106 @@ class WatchCanvasRenderer( } } + val timeTextSize = fun(): Float { + return when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 18f; + } + + else -> { + 14f; + } + } + }() + + val hourOffsetX = fun(): Float { + return when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + 81f; + } + + LayoutStyle.FOCUS.id -> { + 93f; + } + + else -> { + 115f; + } + } + }() + + val hourOffsetY = fun(): Float { + return when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 183f; + } + + else -> { + 185f; + } + } + }() + + val minuteOffsetX = fun(): (Float) { + return when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + 81f + } + + LayoutStyle.FOCUS.id -> { + 93f; + } + + else -> { + 115f; + } + } + }() + + val minuteOffsetY = fun(): (Float) { + return when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 327f + } + + else -> { + 297f; + } + } + }() + + + drawTime2( + canvas, + bounds, + hour, + hourPaint, + hourOffsetX, + hourOffsetY, + timeTextSize + ) + + drawTime2( + canvas, + bounds, + zonedDateTime.minute, + hourPaint, + minuteOffsetX, + minuteOffsetY, + timeTextSize + ) + + +// var hour: Int +// if (is24Format) { +// hour = zonedDateTime.hour +// } else { +// hour = zonedDateTime.hour % 12 +// if (hour == 0) { +// hour = 12 +// } +// } + if (drawProperties.timeScale == 0f) { var hourOffsetX = 0f var hourOffsetY = 0f @@ -664,6 +768,31 @@ class WatchCanvasRenderer( } } + private fun drawTime2( + canvas: Canvas, + bounds: Rect, + time: Int, + paint: Paint, + offsetX: Float, + offsetY: Float, + textSize: Float + ) { + val p = Paint(paint) +// p.textSize = p.textSize // / 384F * bounds.width() + p.textSize *= textSize + + val scale = 1f + + canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.drawText( + time.toString().padStart(2, '0'), + offsetX, + offsetY, + p + ) + } + } + private fun drawTime( canvas: Canvas, bounds: Rect, @@ -673,12 +802,28 @@ class WatchCanvasRenderer( offsetY: Float, scaleOffset: Float ) { + return + // dx:70 dy:98 val p = Paint(paint) p.textSize = p.textSize / 384F * bounds.width() var scale = 1f + when { + watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id + && watchFaceData.secondsStyle.id == SecondsStyle.NONE.id + -> { + scale = scale / 14f * 18f + } + + watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id + && (watchFaceData.secondsStyle.id == SecondsStyle.DASHES.id || watchFaceData.secondsStyle.id == SecondsStyle.DOTS.id) + -> { + scale = scale / 14f * 18f + } + } + p.isAntiAlias = true scale += (1f - this.easeInOutCirc(drawProperties.timeScale)) * scaleOffset @@ -714,17 +859,17 @@ class WatchCanvasRenderer( val transitionOffset: Float = this.easeInOutCirc(1 - this.drawProperties.timeScale) * 16f - canvas.withRotation(secondsRotation, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawRect( - RectF( - (bounds.width() / 2f - 1.25f / 384F * bounds.width()), - transitionOffset / 384F * bounds.width(), - (bounds.width() / 2f + 1.25f / 384F * bounds.width()), - (secondHandSize + transitionOffset * 2) / 384F * bounds.width(), - ), - secondHandPaint, - ) - } +// canvas.withRotation(secondsRotation, bounds.exactCenterX(), bounds.exactCenterY()) { +// canvas.drawRect( +// RectF( +// (bounds.width() / 2f - 1.25f / 384F * bounds.width()), +// transitionOffset / 384F * bounds.width(), +// (bounds.width() / 2f + 1.25f / 384F * bounds.width()), +// (secondHandSize + transitionOffset * 2) / 384F * bounds.width(), +// ), +// secondHandPaint, +// ) +// } val maxAlpha = 255f @@ -740,17 +885,17 @@ class WatchCanvasRenderer( if (i.mod(90f) == 0f) { // cardinals color = watchFaceColors.primaryColor - maxSize = 18f + maxSize = 12f weight = 1.75f minAlpha = maxAlpha / 4f } else if (i.mod(30f) == 0f) { // intermediates color = watchFaceColors.secondaryColor - maxSize = 14f + maxSize = 10f weight = 1.75f minAlpha = maxAlpha / 4f } else { color = watchFaceColors.tertiaryColor - maxSize = 10f + maxSize = 8f weight = 1.5f minAlpha = maxAlpha / 8f } diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt index 2f269bc..956dabf 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt @@ -31,17 +31,20 @@ enum class AmbientStyle( OUTLINE( id = "outline", nameResourceId = R.string.outline_ambient_style_name, - iconResourceId = R.drawable.outline_style_icon, +// iconResourceId = R.drawable.outline_style_icon, + iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon ), BOLD_OUTLINE( id = "bold_outline", nameResourceId = R.string.bold_outline_ambient_style_name, - iconResourceId = R.drawable.bold_outline_style_icon, +// iconResourceId = R.drawable.bold_outline_style_icon, + iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon ), FILLED( id = "filled", nameResourceId = R.string.filled_ambient_style_name, - iconResourceId = R.drawable.filled_style_icon, +// iconResourceId = R.drawable.filled_style_icon, + iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon ); companion object { diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt index 416395d..ee07967 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/LayoutStyle.kt @@ -31,14 +31,24 @@ enum class LayoutStyle( // TODO: @rdnt use SVGs or compress the pngs to conserve wire memory for both // these and other styles (so that we don't exceed transfer limitations of // watchface config over BLE) - DEFAULT( - id = "default", - nameResourceId = R.string.default_layout_style_name, + INFO1( + id = "info1", + nameResourceId = R.string.info1_layout_style_name, iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon ), - FOCUS( - id = "focus", - nameResourceId = R.string.focus_layout_style_name, + INFO2( + id = "info2", + nameResourceId = R.string.info2_layout_style_name, + iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon + ), + INFO3( + id = "info3", + nameResourceId = R.string.info3_layout_style_name, + iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon + ), + INFO4( + id = "info4", + nameResourceId = R.string.info4_layout_style_name, iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon ), SPORT( @@ -46,20 +56,22 @@ enum class LayoutStyle( nameResourceId = R.string.sport_layout_style_name, iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon ), - COMPLICATIONS( - id = "complications", - nameResourceId = R.string.complications_layout_style_name, + FOCUS( + id = "focus", + nameResourceId = R.string.focus_layout_style_name, iconResourceId = R.drawable.steel_style_icon, // TODO @rdnt fix icon ); companion object { fun getLayoutStyleConfig(id: String): LayoutStyle { return when (id) { - DEFAULT.id -> DEFAULT - FOCUS.id -> FOCUS + INFO1.id -> INFO1 + INFO2.id -> INFO2 + INFO3.id -> INFO3 + INFO4.id -> INFO4 SPORT.id -> SPORT - COMPLICATIONS.id -> COMPLICATIONS - else -> DEFAULT + FOCUS.id -> FOCUS + else -> INFO1 } } } diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt index 93e57b9..e047797 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt @@ -19,7 +19,7 @@ package dev.rdnt.m8face.data.watchface * Represents all data needed to render an analog watch face. */ data class WatchFaceData( - val layoutStyle: LayoutStyle = LayoutStyle.DEFAULT, + val layoutStyle: LayoutStyle = LayoutStyle.INFO1, val colorStyle: ColorStyle = ColorStyle.LAST_DANCE, val ambientStyle: AmbientStyle = AmbientStyle.OUTLINE, val secondsStyle: SecondsStyle = SecondsStyle.NONE, diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index 60c0fa6..9701741 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -1027,15 +1027,84 @@ fun ComplicationPicker( .fillMaxSize() ) { + when (layoutIndex) { + 0, 1, 2, 3 -> { + ComplicationButton( + stateHolder, + HOUR_COMPLICATION_ID, + RectF( + HOUR_COMPLICATION_LEFT_BOUND + 6f / 384f, + HOUR_COMPLICATION_TOP_BOUND + 6f / 384f, + HOUR_COMPLICATION_RIGHT_BOUND - 6f / 384f, + HOUR_COMPLICATION_BOTTOM_BOUND - 6f / 384f, + ), + ) + ComplicationButton( + stateHolder, + MINUTE_COMPLICATION_ID, + RectF( + MINUTE_COMPLICATION_LEFT_BOUND + 6f / 384f, + MINUTE_COMPLICATION_TOP_BOUND + 6f / 384f, + MINUTE_COMPLICATION_RIGHT_BOUND - 6f / 384f, + MINUTE_COMPLICATION_BOTTOM_BOUND - 6f / 384f, + ), + ) + } + 4 -> { + ComplicationButton( + stateHolder, + HOUR_COMPLICATION_ID, + RectF( + HOUR_SPORT_COMPLICATION_LEFT_BOUND + 6f / 384f, + HOUR_SPORT_COMPLICATION_TOP_BOUND + 6f / 384f, + HOUR_SPORT_COMPLICATION_RIGHT_BOUND - 6f / 384f, + HOUR_SPORT_COMPLICATION_BOTTOM_BOUND - 6f / 384f, + ), + ) + ComplicationButton( + stateHolder, + MINUTE_COMPLICATION_ID, + RectF( + MINUTE_SPORT_COMPLICATION_LEFT_BOUND, + MINUTE_SPORT_COMPLICATION_TOP_BOUND + 6f / 384f, + MINUTE_SPORT_COMPLICATION_RIGHT_BOUND - 6f / 384f, + MINUTE_SPORT_COMPLICATION_BOTTOM_BOUND - 6f / 384f, + ), + ) + } + 5 -> { + ComplicationButton( + stateHolder, + HOUR_COMPLICATION_ID, + RectF( + HOUR_FOCUS_COMPLICATION_LEFT_BOUND + 6f / 384f, + HOUR_FOCUS_COMPLICATION_TOP_BOUND + 6f / 384f, + HOUR_FOCUS_COMPLICATION_RIGHT_BOUND - 6f / 384f, + HOUR_FOCUS_COMPLICATION_BOTTOM_BOUND - 6f / 384f, + ), + ) + ComplicationButton( + stateHolder, + MINUTE_COMPLICATION_ID, + RectF( + MINUTE_FOCUS_COMPLICATION_LEFT_BOUND + 6f / 384f, + MINUTE_FOCUS_COMPLICATION_TOP_BOUND + 6f / 384f, + MINUTE_FOCUS_COMPLICATION_RIGHT_BOUND - 6f / 384f, + MINUTE_FOCUS_COMPLICATION_BOTTOM_BOUND - 6f / 384f, + ), + ) + } + } + when (layoutIndex) { 0 -> { ComplicationButton( stateHolder, TOP_COMPLICATION_ID, RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_LEFT_BOUND, TOP_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_RIGHT_BOUND, TOP_COMPLICATION_BOTTOM_BOUND, ), ) @@ -1044,9 +1113,9 @@ fun ComplicationPicker( stateHolder, BOTTOM_COMPLICATION_ID, RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_LEFT_BOUND, BOTTOM_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_RIGHT_BOUND, BOTTOM_COMPLICATION_BOTTOM_BOUND, ), ) @@ -1061,49 +1130,61 @@ fun ComplicationPicker( VERTICAL_COMPLICATION_BOTTOM_BOUND, ), ) + } + 1 -> { + ComplicationButton( + stateHolder, + TOP_COMPLICATION_ID, + RectF( + TOP_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_TOP_BOUND, + TOP_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_BOTTOM_BOUND, + ), + ) ComplicationButton( stateHolder, - RIGHT_COMPLICATION_ID, + BOTTOM_COMPLICATION_ID, RectF( - RIGHT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, - RIGHT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND, + BOTTOM_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_TOP_BOUND, + BOTTOM_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_BOTTOM_BOUND, ), ) - } - 1 -> { + ComplicationButton( stateHolder, - LEFT_ICON_COMPLICATION_ID, + LEFT_COMPLICATION_ID, RectF( - LEFT_ICON_COMPLICATION_LEFT_BOUND, - LEFT_ICON_COMPLICATION_TOP_BOUND, - LEFT_ICON_COMPLICATION_RIGHT_BOUND, - LEFT_ICON_COMPLICATION_BOTTOM_BOUND, + LEFT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND, ), ) ComplicationButton( stateHolder, - RIGHT_ICON_COMPLICATION_ID, + RIGHT_COMPLICATION_ID, RectF( - RIGHT_ICON_COMPLICATION_LEFT_BOUND, - RIGHT_ICON_COMPLICATION_TOP_BOUND, - RIGHT_ICON_COMPLICATION_RIGHT_BOUND, - RIGHT_ICON_COMPLICATION_BOTTOM_BOUND, + RIGHT_COMPLICATION_LEFT_BOUND, + VERTICAL_COMPLICATION_TOP_BOUND, + RIGHT_COMPLICATION_RIGHT_BOUND, + VERTICAL_COMPLICATION_BOTTOM_BOUND, ), ) } + 2 -> { ComplicationButton( stateHolder, TOP_COMPLICATION_ID, RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_LEFT_BOUND, TOP_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_RIGHT_BOUND, TOP_COMPLICATION_BOTTOM_BOUND, ), ) @@ -1112,32 +1193,44 @@ fun ComplicationPicker( stateHolder, BOTTOM_COMPLICATION_ID, RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_LEFT_BOUND, BOTTOM_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_RIGHT_BOUND, BOTTOM_COMPLICATION_BOTTOM_BOUND, ), ) ComplicationButton( stateHolder, - RIGHT_TEXT_COMPLICATION_ID, + COMPLICATIONS_TOP_LEFT_COMPLICATION_ID, RectF( - RIGHT_TEXT_COMPLICATION_LEFT_BOUND, - RIGHT_TEXT_COMPLICATION_TOP_BOUND, - RIGHT_TEXT_COMPLICATION_RIGHT_BOUND, - RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND, + TOP_LEFT_COMPLICATION_LEFT_BOUND - 3f / 384f, + TOP_LEFT_COMPLICATION_TOP_BOUND - 6f / 384f, + TOP_LEFT_COMPLICATION_RIGHT_BOUND + 3f / 384f, + TOP_LEFT_COMPLICATION_BOTTOM_BOUND + 6f / 384f, + ), + ) + + ComplicationButton( + stateHolder, + COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID, + RectF( + BOTTOM_LEFT_COMPLICATION_LEFT_BOUND - 3f / 384f, + BOTTOM_LEFT_COMPLICATION_TOP_BOUND - 6f / 384f, + BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND + 3f / 384f, + BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND + 6f / 384f, ), ) } + 3-> { ComplicationButton( stateHolder, TOP_COMPLICATION_ID, RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_LEFT_BOUND, TOP_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_RIGHT_BOUND, TOP_COMPLICATION_BOTTOM_BOUND, ), ) @@ -1146,54 +1239,113 @@ fun ComplicationPicker( stateHolder, BOTTOM_COMPLICATION_ID, RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_LEFT_BOUND, BOTTOM_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_RIGHT_BOUND, BOTTOM_COMPLICATION_BOTTOM_BOUND, ), ) ComplicationButton( stateHolder, - TOP_LEFT_COMPLICATION_ID, + COMPLICATIONS_TOP_LEFT_COMPLICATION_ID, + RectF( + TOP_LEFT_COMPLICATION_LEFT_BOUND - 3f / 384f, + TOP_LEFT_COMPLICATION_TOP_BOUND - 6f / 384f, + TOP_LEFT_COMPLICATION_RIGHT_BOUND + 3f / 384f, + TOP_LEFT_COMPLICATION_BOTTOM_BOUND + 6f / 384f, + ), + ) + + ComplicationButton( + stateHolder, + COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID, + RectF( + BOTTOM_LEFT_COMPLICATION_LEFT_BOUND - 3f / 384f, + BOTTOM_LEFT_COMPLICATION_TOP_BOUND - 6f / 384f, + BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND + 3f / 384f, + BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND + 6f / 384f, + ), + ) + + ComplicationButton( + stateHolder, + COMPLICATIONS_TOP_RIGHT_COMPLICATION_ID, + RectF( + TOP_RIGHT_COMPLICATION_LEFT_BOUND - 3f / 384f, + TOP_RIGHT_COMPLICATION_TOP_BOUND - 6f / 384f, + TOP_RIGHT_COMPLICATION_RIGHT_BOUND + 3f / 384f, + TOP_RIGHT_COMPLICATION_BOTTOM_BOUND + 6f / 384f, + ), + ) + + ComplicationButton( + stateHolder, + COMPLICATIONS_BOTTOM_RIGHT_COMPLICATION_ID, RectF( - TOP_LEFT_COMPLICATION_LEFT_BOUND, - TOP_LEFT_COMPLICATION_TOP_BOUND, - TOP_LEFT_COMPLICATION_RIGHT_BOUND, - TOP_LEFT_COMPLICATION_BOTTOM_BOUND, + BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND - 3f / 384f, + BOTTOM_RIGHT_COMPLICATION_TOP_BOUND - 6f / 384f, + BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND + 3f / 384f, + BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND + 6f / 384f, ), ) + } + 4 -> { ComplicationButton( stateHolder, - BOTTOM_LEFT_COMPLICATION_ID, + TOP_COMPLICATION_ID, RectF( - BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, - BOTTOM_LEFT_COMPLICATION_TOP_BOUND, - BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, - BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND, + TOP_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_TOP_BOUND, + TOP_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_BOTTOM_BOUND, ), ) ComplicationButton( stateHolder, - TOP_RIGHT_COMPLICATION_ID, + BOTTOM_COMPLICATION_ID, RectF( - TOP_RIGHT_COMPLICATION_LEFT_BOUND, - TOP_RIGHT_COMPLICATION_TOP_BOUND, - TOP_RIGHT_COMPLICATION_RIGHT_BOUND, - TOP_RIGHT_COMPLICATION_BOTTOM_BOUND, + BOTTOM_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_TOP_BOUND, + BOTTOM_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_BOTTOM_BOUND, ), ) ComplicationButton( stateHolder, - BOTTOM_RIGHT_COMPLICATION_ID, + RIGHT_TEXT_COMPLICATION_ID, RectF( - BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, - BOTTOM_RIGHT_COMPLICATION_TOP_BOUND, - BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, - BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND, + RIGHT_TEXT_COMPLICATION_LEFT_BOUND, + RIGHT_TEXT_COMPLICATION_TOP_BOUND, + RIGHT_TEXT_COMPLICATION_RIGHT_BOUND, + RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND, + ), + ) + } + + 5 -> { + ComplicationButton( + stateHolder, + FOCUS_LEFT_ICON_COMPLICATION_ID, + RectF( + LEFT_ICON_COMPLICATION_LEFT_BOUND - 9f / 384f, + LEFT_ICON_COMPLICATION_TOP_BOUND + 6f / 384f, + LEFT_ICON_COMPLICATION_RIGHT_BOUND + 9f / 384f, + LEFT_ICON_COMPLICATION_BOTTOM_BOUND - 6f / 384f, + ), + ) + + ComplicationButton( + stateHolder, + FOCUS_RIGHT_ICON_COMPLICATION_ID, + RectF( + RIGHT_ICON_COMPLICATION_LEFT_BOUND - 9f / 384f, + RIGHT_ICON_COMPLICATION_TOP_BOUND + 6f / 384f, + RIGHT_ICON_COMPLICATION_RIGHT_BOUND + 9f / 384f, + RIGHT_ICON_COMPLICATION_BOTTOM_BOUND - 6f / 384f, ), ) } @@ -1210,12 +1362,17 @@ fun ComplicationButton( bounds: RectF, // left: Float, top: Float, right: Float, bottom: Float ) { - var left= bounds.left - 0.015625f - var right= bounds.right + 0.015625f - var top= bounds.top - 0.015625f - var bottom= bounds.bottom + 0.015625f +// var left= bounds.left - 6f / 384f +// var right= bounds.right + 6f / 384f +// var top= bounds.top - 6f / 384f +// var bottom= bounds.bottom + 6f / 384f + + var left= bounds.left + var right= bounds.right + var top= bounds.top + var bottom= bounds.bottom - Log.d("Editor", "ComplicationButton(${left}, ${top}, ${right}, ${bottom})") +// Log.d("Editor", "ComplicationButton(${left}, ${top}, ${right}, ${bottom})") Row(Modifier.fillMaxSize()) { Box(Modifier.weight(left, true)) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index 55834ac..20222f0 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -202,34 +202,42 @@ class WatchFaceConfigStateHolder( BOTTOM_COMPLICATION_ID } - TOP_LEFT_COMPLICATION_ID -> { - TOP_LEFT_COMPLICATION_ID + COMPLICATIONS_TOP_LEFT_COMPLICATION_ID -> { + COMPLICATIONS_TOP_LEFT_COMPLICATION_ID } - BOTTOM_LEFT_COMPLICATION_ID -> { - BOTTOM_LEFT_COMPLICATION_ID + COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID -> { + COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID } - TOP_RIGHT_COMPLICATION_ID -> { - TOP_RIGHT_COMPLICATION_ID + COMPLICATIONS_TOP_RIGHT_COMPLICATION_ID -> { + COMPLICATIONS_TOP_RIGHT_COMPLICATION_ID } - BOTTOM_RIGHT_COMPLICATION_ID -> { - BOTTOM_RIGHT_COMPLICATION_ID + COMPLICATIONS_BOTTOM_RIGHT_COMPLICATION_ID -> { + COMPLICATIONS_BOTTOM_RIGHT_COMPLICATION_ID } - LEFT_ICON_COMPLICATION_ID -> { - LEFT_ICON_COMPLICATION_ID + FOCUS_LEFT_ICON_COMPLICATION_ID -> { + FOCUS_LEFT_ICON_COMPLICATION_ID } - RIGHT_ICON_COMPLICATION_ID -> { - RIGHT_ICON_COMPLICATION_ID + FOCUS_RIGHT_ICON_COMPLICATION_ID -> { + FOCUS_RIGHT_ICON_COMPLICATION_ID } RIGHT_TEXT_COMPLICATION_ID -> { RIGHT_TEXT_COMPLICATION_ID } + HOUR_COMPLICATION_ID -> { + HOUR_COMPLICATION_ID + } + + MINUTE_COMPLICATION_ID -> { + MINUTE_COMPLICATION_ID + } + else -> { launchInProgress = false return diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index c89d215..c1c5577 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -16,32 +16,16 @@ package dev.rdnt.m8face.utils import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.graphics.Rect import android.graphics.RectF -import android.util.Log -import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.toBitmap -import androidx.wear.watchface.CanvasComplication +import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.ComplicationSlot import androidx.wear.watchface.ComplicationSlotsManager -import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.complications.ComplicationSlotBounds import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy import androidx.wear.watchface.complications.SystemDataSources -import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType -import androidx.wear.watchface.complications.data.NoDataComplicationData -import androidx.wear.watchface.complications.data.ShortTextComplicationData import androidx.wear.watchface.style.CurrentUserStyleRepository import dev.rdnt.m8face.R -import java.time.Instant -import java.time.ZonedDateTime // Information needed for complications. // Creates bounds for the locations of both right and left complications. (This is the @@ -51,7 +35,7 @@ const val VERTICAL_COMPLICATION_TOP_BOUND = 0.328125f // 126px / 384 const val VERTICAL_COMPLICATION_BOTTOM_BOUND = 1f - VERTICAL_COMPLICATION_TOP_BOUND // offset: 18px, width: 78px (canvas 384x384) -const val VERTICAL_COMPLICATION_OFFSET = 24f / 384f +const val VERTICAL_COMPLICATION_OFFSET = 21f / 384f const val VERTICAL_COMPLICATION_WIDTH = 78f / 384f //const val VERTICAL_COMPLICATION_HEIGHT = VERTICAL_COMPLICATION_BOTTOM_BOUND - VERTICAL_COMPLICATION_TOP_BOUND @@ -60,10 +44,14 @@ const val VERTICAL_COMPLICATION_WIDTH = 78f / 384f const val LEFT_COMPLICATION_LEFT_BOUND = VERTICAL_COMPLICATION_OFFSET const val LEFT_COMPLICATION_RIGHT_BOUND = VERTICAL_COMPLICATION_OFFSET + VERTICAL_COMPLICATION_WIDTH +const val LEFT_COMPLICATION_TOP_BOUND = VERTICAL_COMPLICATION_TOP_BOUND +const val LEFT_COMPLICATION_BOTTOM_BOUND = VERTICAL_COMPLICATION_BOTTOM_BOUND const val RIGHT_COMPLICATION_LEFT_BOUND = 1f - VERTICAL_COMPLICATION_OFFSET - VERTICAL_COMPLICATION_WIDTH const val RIGHT_COMPLICATION_RIGHT_BOUND = 1f - VERTICAL_COMPLICATION_OFFSET +const val RIGHT_COMPLICATION_TOP_BOUND = VERTICAL_COMPLICATION_TOP_BOUND +const val RIGHT_COMPLICATION_BOTTOM_BOUND = VERTICAL_COMPLICATION_BOTTOM_BOUND // Both left and right complications use the same top and bottom bounds. const val HORIZONTAL_COMPLICATION_LEFT_BOUND = 99f / 384f @@ -78,10 +66,14 @@ const val HORIZONTAL_COMPLICATION_HEIGHT = 48f / 384f const val TOP_COMPLICATION_TOP_BOUND = HORIZONTAL_COMPLICATION_OFFSET const val TOP_COMPLICATION_BOTTOM_BOUND = HORIZONTAL_COMPLICATION_OFFSET + HORIZONTAL_COMPLICATION_HEIGHT +const val TOP_COMPLICATION_LEFT_BOUND = HORIZONTAL_COMPLICATION_LEFT_BOUND +const val TOP_COMPLICATION_RIGHT_BOUND = HORIZONTAL_COMPLICATION_RIGHT_BOUND const val BOTTOM_COMPLICATION_TOP_BOUND = 1f - HORIZONTAL_COMPLICATION_OFFSET - HORIZONTAL_COMPLICATION_HEIGHT const val BOTTOM_COMPLICATION_BOTTOM_BOUND = 1f - HORIZONTAL_COMPLICATION_OFFSET +const val BOTTOM_COMPLICATION_LEFT_BOUND = HORIZONTAL_COMPLICATION_LEFT_BOUND +const val BOTTOM_COMPLICATION_RIGHT_BOUND = HORIZONTAL_COMPLICATION_RIGHT_BOUND // Unique IDs for each complication. The settings activity that supports allowing users // to select their complication data provider requires numbers to be >= 0. @@ -89,42 +81,55 @@ internal const val LEFT_COMPLICATION_ID = 100 internal const val RIGHT_COMPLICATION_ID = 101 internal const val TOP_COMPLICATION_ID = 102 internal const val BOTTOM_COMPLICATION_ID = 103 -internal const val TOP_LEFT_COMPLICATION_ID = 104 -internal const val BOTTOM_LEFT_COMPLICATION_ID = 105 -internal const val TOP_RIGHT_COMPLICATION_ID = 106 -internal const val BOTTOM_RIGHT_COMPLICATION_ID = 107 -internal const val LEFT_ICON_COMPLICATION_ID = 108 -internal const val RIGHT_ICON_COMPLICATION_ID = 109 -internal const val RIGHT_TEXT_COMPLICATION_ID = 110 +internal const val HOUR_COMPLICATION_ID = 104 +internal const val MINUTE_COMPLICATION_ID = 105 + +internal const val COMPLICATIONS_TOP_LEFT_COMPLICATION_ID = 106 +internal const val COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID = 107 +internal const val COMPLICATIONS_TOP_RIGHT_COMPLICATION_ID = 108 +internal const val COMPLICATIONS_BOTTOM_RIGHT_COMPLICATION_ID = 109 +internal const val COMPLICATIONS_TOP_COMPLICATION_ID = 110 +internal const val COMPLICATIONS_BOTTOM_COMPLICATION_ID = 111 +internal const val COMPLICATIONS_HOUR_COMPLICATION_ID = 112 +internal const val COMPLICATIONS_MINUTE_COMPLICATION_ID = 113 + +internal const val FOCUS_LEFT_ICON_COMPLICATION_ID = 114 +internal const val FOCUS_RIGHT_ICON_COMPLICATION_ID = 115 +internal const val FOCUS_HOUR_COMPLICATION_ID = 116 +internal const val FOCUS_MINUTE_COMPLICATION_ID = 117 + +internal const val RIGHT_TEXT_COMPLICATION_ID = 118 +internal const val SPORT_HOUR_COMPLICATION_ID = 119 +internal const val SPORT_MINUTE_COMPLICATION_ID = 120 const val TOP_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f -const val TOP_LEFT_COMPLICATION_TOP_BOUND = 84f / 384f -const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f -const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 84f / 384f + 96f / 384f +const val TOP_LEFT_COMPLICATION_TOP_BOUND = 93f / 384f +const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 66f / 384f +const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 93f / 384f + 87f / 384f const val BOTTOM_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 204f / 384f -const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 72f / 384f -const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 204f / 384f + 96f / 384f +const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 66f / 384f +const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 204f / 384f + 87f / 384f -const val TOP_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f -const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 84f / 384f -const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f -const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 84f / 384f + 96f / 384f +const val TOP_RIGHT_COMPLICATION_LEFT_BOUND = 285f / 384f +const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 93f / 384f +const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 285f / 384f + 66f / 384f +const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 93f / 384f + 87f / 384f -const val BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND = 279f / 384f +const val BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND = 285f / 384f const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 204f / 384f -const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 279f / 384f + 72f / 384f -const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 204f / 384f + 96f / 384f +const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 285f / 384f + 66f / 384f +const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 204f / 384f + 87f / 384f -const val LEFT_ICON_COMPLICATION_LEFT_BOUND = 33f / 384f +const val LEFT_ICON_COMPLICATION_LEFT_BOUND = 24f / 384f const val LEFT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f -const val LEFT_ICON_COMPLICATION_RIGHT_BOUND = 33f / 384f + 54f / 384f +const val LEFT_ICON_COMPLICATION_RIGHT_BOUND = 24f / 384f + 54f / 384f const val LEFT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f -const val RIGHT_ICON_COMPLICATION_LEFT_BOUND = 297f / 384f +const val RIGHT_ICON_COMPLICATION_LEFT_BOUND = 306f / 384f const val RIGHT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f -const val RIGHT_ICON_COMPLICATION_RIGHT_BOUND = 297f / 384f + 54f / 384f +const val RIGHT_ICON_COMPLICATION_RIGHT_BOUND = 306f / 384f + 54f / 384f const val RIGHT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f const val RIGHT_TEXT_COMPLICATION_LEFT_BOUND = 248f / 384f @@ -132,248 +137,605 @@ const val RIGHT_TEXT_COMPLICATION_TOP_BOUND = 248f / 384f const val RIGHT_TEXT_COMPLICATION_RIGHT_BOUND = 248f / 384f + 66f / 384f const val RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND = 248f / 384f + 15f / 384f -/** - * Represents the unique id associated with a complication and the complication types it supports. - */ -sealed class ComplicationConfig(val id: Int, val supportedTypes: List) { - object Left : ComplicationConfig( - LEFT_COMPLICATION_ID, listOf( - ComplicationType.SHORT_TEXT, - ComplicationType.MONOCHROMATIC_IMAGE, - ComplicationType.SMALL_IMAGE - ) - ) - - object Right : ComplicationConfig( - RIGHT_COMPLICATION_ID, listOf( - ComplicationType.SHORT_TEXT, - ComplicationType.MONOCHROMATIC_IMAGE, - ComplicationType.SMALL_IMAGE - ) - ) - - object Top : ComplicationConfig( - TOP_COMPLICATION_ID, listOf( - ComplicationType.SHORT_TEXT, - ) - ) +const val HOUR_COMPLICATION_LEFT_BOUND = 114f / 384f +const val HOUR_COMPLICATION_TOP_BOUND = 87f / 384f +const val HOUR_COMPLICATION_RIGHT_BOUND = 114f / 384f + 156f / 384f +const val HOUR_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 99f / 384f + +const val HOUR_SPORT_COMPLICATION_LEFT_BOUND = 81f / 384f +const val HOUR_SPORT_COMPLICATION_TOP_BOUND = 87f / 384f +const val HOUR_SPORT_COMPLICATION_RIGHT_BOUND = 81f / 384f + 156f / 384f +const val HOUR_SPORT_COMPLICATION_BOTTOM_BOUND = 87f / 384f + 102f / 384f + +const val HOUR_FOCUS_COMPLICATION_LEFT_BOUND = 93f / 384f +const val HOUR_FOCUS_COMPLICATION_TOP_BOUND = 57f / 384f +const val HOUR_FOCUS_COMPLICATION_RIGHT_BOUND = 93f / 384f + 198f / 384f +const val HOUR_FOCUS_COMPLICATION_BOTTOM_BOUND = 57f / 384f + 126f / 384f + +const val MINUTE_COMPLICATION_LEFT_BOUND = 114f / 384f +const val MINUTE_COMPLICATION_TOP_BOUND = 198f / 384f +const val MINUTE_COMPLICATION_RIGHT_BOUND = 114f / 384f + 156f / 384f +const val MINUTE_COMPLICATION_BOTTOM_BOUND = 198f / 384f + 99f / 384f + +const val MINUTE_SPORT_COMPLICATION_LEFT_BOUND = 81f / 384f +const val MINUTE_SPORT_COMPLICATION_TOP_BOUND = 198f / 384f +const val MINUTE_SPORT_COMPLICATION_RIGHT_BOUND = 81f / 384f + 156f / 384f +const val MINUTE_SPORT_COMPLICATION_BOTTOM_BOUND = 198f / 384f + 102f / 384f + +const val MINUTE_FOCUS_COMPLICATION_LEFT_BOUND = 93f / 384f +const val MINUTE_FOCUS_COMPLICATION_TOP_BOUND = 201f / 384f +const val MINUTE_FOCUS_COMPLICATION_RIGHT_BOUND = 93f / 384f + 198f / 384f +const val MINUTE_FOCUS_COMPLICATION_BOTTOM_BOUND = 201f / 384f + 126f / 384f + +///** +// * Represents the unique id associated with a complication and the complication types it supports. +// */ +//sealed class ComplicationConfigDelete( +// val id: Int, +// val supportedTypes: List, +// val renderBounds: RectF = RectF(), +// val slotBounds: RectF = RectF(), +// val nameResourceId: Int?, +//) { +// object Hour : ComplicationConfigDelete( +// HOUR_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// slotBounds = RectF( +// HOUR_COMPLICATION_LEFT_BOUND, +// HOUR_COMPLICATION_TOP_BOUND, +// HOUR_COMPLICATION_RIGHT_BOUND, +// HOUR_COMPLICATION_BOTTOM_BOUND, +// ), +// renderBounds = RectF( +// HOUR_COMPLICATION_LEFT_BOUND, +// HOUR_COMPLICATION_TOP_BOUND, +// HOUR_COMPLICATION_RIGHT_BOUND, +// HOUR_COMPLICATION_BOTTOM_BOUND, +// ), +// nameResourceId = R.string.hour_complication_name, +// ) +// +// object Minute : ComplicationConfigDelete( +// MINUTE_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// slotBounds = RectF( +// MINUTE_COMPLICATION_LEFT_BOUND, +// MINUTE_COMPLICATION_TOP_BOUND, +// MINUTE_COMPLICATION_RIGHT_BOUND, +// MINUTE_COMPLICATION_BOTTOM_BOUND, +// ), +// renderBounds = RectF( +// MINUTE_COMPLICATION_LEFT_BOUND, +// MINUTE_COMPLICATION_TOP_BOUND, +// MINUTE_COMPLICATION_RIGHT_BOUND, +// MINUTE_COMPLICATION_BOTTOM_BOUND, +// ), +// nameResourceId = R.string.minute_complication_name, +// ) +// +// object Left : ComplicationConfigDelete( +// LEFT_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// renderBounds = RectF( +// LEFT_COMPLICATION_LEFT_BOUND, +// LEFT_COMPLICATION_TOP_BOUND, +// LEFT_COMPLICATION_RIGHT_BOUND, +// LEFT_COMPLICATION_BOTTOM_BOUND, +// ), +// slotBounds = RectF( +// 0f / 384f, +// 87f / 384f, +// 0f / 384f + 108f / 384f, +// 87f / 384f + 210f / 384f, +// ), +// nameResourceId = R.string.left_complication_name, +// ) +// +// object Right : ComplicationConfigDelete( +// RIGHT_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// renderBounds = RectF( +// RIGHT_COMPLICATION_LEFT_BOUND, +// RIGHT_COMPLICATION_TOP_BOUND, +// RIGHT_COMPLICATION_RIGHT_BOUND, +// RIGHT_COMPLICATION_BOTTOM_BOUND, +// ), +// slotBounds = RectF( +// 276f / 384f, +// 87f / 384f, +// 276f / 384f + 108f / 384f, +// 87f / 384f + 210f / 384f +// ), +// nameResourceId = R.string.right_complication_name, +// ) +// +// object Top : ComplicationConfigDelete( +// TOP_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ), +// renderBounds = RectF( +// TOP_COMPLICATION_LEFT_BOUND, +// TOP_COMPLICATION_TOP_BOUND, +// TOP_COMPLICATION_RIGHT_BOUND, +// TOP_COMPLICATION_BOTTOM_BOUND, +// ), +// slotBounds = RectF( +// 0f / 384f, +// 0f / 384f, +// 0f / 384f + 384f / 384f, +// 0f / 384f + 81f / 384f, +// ), +// nameResourceId = R.string.top_complication_name, +// ) +// +// object Bottom : ComplicationConfigDelete( +// BOTTOM_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ), +// renderBounds = RectF( +// BOTTOM_COMPLICATION_LEFT_BOUND, +// BOTTOM_COMPLICATION_TOP_BOUND, +// BOTTOM_COMPLICATION_RIGHT_BOUND, +// BOTTOM_COMPLICATION_BOTTOM_BOUND, +// ), +// slotBounds = RectF( +// 0f / 384f, +// 303f / 384f, +// 0f / 384f + 384f / 384f, +// 303f / 384f + 81f / 384f, +// ), +// nameResourceId = R.string.bottom_complication_name, +// ) +// +// // ========================================================================== +// +// object TopLeft : ComplicationConfigDelete( +// COMPLICATIONS_TOP_LEFT_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// renderBounds = RectF( +// TOP_LEFT_COMPLICATION_LEFT_BOUND, +// TOP_LEFT_COMPLICATION_TOP_BOUND-6f/384f, +// TOP_LEFT_COMPLICATION_RIGHT_BOUND, +// TOP_LEFT_COMPLICATION_BOTTOM_BOUND+6f/384f, +// ), +// slotBounds = RectF( +// 0f / 384f, +// 87f / 384f, +// 0f / 384f + 108f / 384f, +// 87f / 384f + 102f / 384f, +// ), +// nameResourceId = R.string.top_left_complication_name, +// ) +// +// object BottomLeft : ComplicationConfigDelete( +// COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// renderBounds = RectF( +// BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, +// BOTTOM_LEFT_COMPLICATION_TOP_BOUND-6f/384f, +// BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, +// BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND+6f/384f, +// ), +// slotBounds = RectF( +// 0f / 384f, +// 195f / 384f, +// 0f / 384f + 108f / 384f, +// 195f / 384f + 102f / 384f, +// ), +// nameResourceId = R.string.bottom_left_complication_name, +// ) +// +// object TopRight : ComplicationConfigDelete( +// COMPLICATIONS_TOP_RIGHT_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// renderBounds = RectF( +// TOP_RIGHT_COMPLICATION_LEFT_BOUND, +// TOP_RIGHT_COMPLICATION_TOP_BOUND-6f/384f, +// TOP_RIGHT_COMPLICATION_RIGHT_BOUND, +// TOP_RIGHT_COMPLICATION_BOTTOM_BOUND+6f/384f, +// ), +// slotBounds = RectF( +// 276f / 384f, +// 87f / 384f, +// 276f / 384f + 108f / 384f, +// 87f / 384f + 102f / 384f, +// ), +// nameResourceId = R.string.top_right_complication_name, +// ) +// +// object BottomRight : ComplicationConfigDelete( +// COMPLICATIONS_BOTTOM_RIGHT_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// renderBounds = RectF( +// BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, +// BOTTOM_RIGHT_COMPLICATION_TOP_BOUND-6f/384f, +// BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, +// BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND+6f/384f, +// ), +// slotBounds = RectF( +// 276f / 384f, +// 195f / 384f, +// 276f / 384f + 108f / 384f, +// 195f / 384f + 102f / 384f, +// ), +// nameResourceId = R.string.bottom_right_complication_name, +// ) +// +//// object ComplicationsTop : ComplicationConfigDelete( +//// COMPLICATIONS_TOP_COMPLICATION_ID, listOf( +//// ComplicationType.SHORT_TEXT, +//// ), +//// renderBounds = RectF( +//// TOP_COMPLICATION_LEFT_BOUND, +//// TOP_COMPLICATION_TOP_BOUND, +//// TOP_COMPLICATION_RIGHT_BOUND, +//// TOP_COMPLICATION_BOTTOM_BOUND, +//// ), +//// slotBounds = RectF( +//// 0f / 384f, +//// 0f / 384f, +//// 0f / 384f + 384f / 384f, +//// 0f / 384f + 81f / 384f, +//// ), +//// ) +// +//// object ComplicationsBottom : ComplicationConfigDelete( +//// COMPLICATIONS_BOTTOM_COMPLICATION_ID, listOf( +//// ComplicationType.SHORT_TEXT, +//// ), +//// renderBounds = RectF( +//// BOTTOM_COMPLICATION_LEFT_BOUND, +//// BOTTOM_COMPLICATION_TOP_BOUND, +//// BOTTOM_COMPLICATION_RIGHT_BOUND, +//// BOTTOM_COMPLICATION_BOTTOM_BOUND, +//// ), +//// slotBounds = RectF( +//// 0f / 384f, +//// 303f / 384f, +//// 0f / 384f + 384f / 384f, +//// 303f / 384f + 81f / 384f, +//// ), +//// ) +// +// // ========================================================================== +// +// object LeftIcon : ComplicationConfigDelete( +// FOCUS_LEFT_ICON_COMPLICATION_ID, listOf( +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// renderBounds = RectF( +// LEFT_ICON_COMPLICATION_LEFT_BOUND, +// LEFT_ICON_COMPLICATION_TOP_BOUND, +// LEFT_ICON_COMPLICATION_RIGHT_BOUND, +// LEFT_ICON_COMPLICATION_BOTTOM_BOUND +// ), +// slotBounds = RectF( +// 0f / 384f, +// 0f / 384f, +// 0f / 384f + 93f / 384f, +// 0f / 384f + 384f / 384f +// ), +// nameResourceId = R.string.left_complication_name, +// ) +// +// object RightIcon : ComplicationConfigDelete( +// FOCUS_RIGHT_ICON_COMPLICATION_ID, listOf( +// ComplicationType.MONOCHROMATIC_IMAGE, +// ComplicationType.SMALL_IMAGE +// ), +// renderBounds = RectF( +// RIGHT_ICON_COMPLICATION_LEFT_BOUND, +// RIGHT_ICON_COMPLICATION_TOP_BOUND, +// RIGHT_ICON_COMPLICATION_RIGHT_BOUND, +// RIGHT_ICON_COMPLICATION_BOTTOM_BOUND +// ), +// slotBounds = RectF( +// 291f / 384f, +// 0f / 384f, +// 291f / 384f + 93f / 384f, +// 0f / 384f + 384f / 384f +// ), +// nameResourceId = R.string.right_complication_name, +// ) +// +// object Text : ComplicationConfigDelete( +// RIGHT_TEXT_COMPLICATION_ID, listOf( +// ComplicationType.SHORT_TEXT, +// ), +// renderBounds = RectF( +// RIGHT_TEXT_COMPLICATION_LEFT_BOUND, +// RIGHT_TEXT_COMPLICATION_TOP_BOUND, +// RIGHT_TEXT_COMPLICATION_RIGHT_BOUND, +// RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND +// ), +// slotBounds = RectF( +// RIGHT_TEXT_COMPLICATION_LEFT_BOUND, +// RIGHT_TEXT_COMPLICATION_TOP_BOUND, +// RIGHT_TEXT_COMPLICATION_RIGHT_BOUND, +// RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND +// ), +// nameResourceId = R.string.right_complication_name, +// ) +//} - object Bottom : ComplicationConfig( - BOTTOM_COMPLICATION_ID, listOf( - ComplicationType.SHORT_TEXT, - ) - ) - - object TopLeft : ComplicationConfig( - TOP_LEFT_COMPLICATION_ID, listOf( - ComplicationType.SHORT_TEXT, - ComplicationType.MONOCHROMATIC_IMAGE, - ComplicationType.SMALL_IMAGE - ) - ) - - object BottomLeft : ComplicationConfig( - BOTTOM_LEFT_COMPLICATION_ID, listOf( +// Utility function that initializes default complication slots (left and right). +fun createComplicationSlotManager( + context: Context, + currentUserStyleRepository: CurrentUserStyleRepository, +): ComplicationSlotsManager { + val iconComplicationFactory = createIconComplicationFactory(context) + val verticalComplicationFactory = createVerticalComplicationFactory(context) + val horizontalComplicationFactory = createHorizontalComplicationFactory(context) + val invisibleComplicationFactory = createInvisibleComplicationFactory(context) + + val hourComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = HOUR_COMPLICATION_ID, + canvasComplicationFactory = invisibleComplicationFactory, + supportedTypes = listOf( ComplicationType.SHORT_TEXT, ComplicationType.MONOCHROMATIC_IMAGE, ComplicationType.SMALL_IMAGE + ), + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT + ), + bounds = ComplicationSlotBounds( + RectF( + HOUR_COMPLICATION_LEFT_BOUND, + HOUR_COMPLICATION_TOP_BOUND, + HOUR_COMPLICATION_RIGHT_BOUND, + HOUR_COMPLICATION_BOTTOM_BOUND, + ), ) - ) - - object TopRight : ComplicationConfig( - TOP_RIGHT_COMPLICATION_ID, listOf( + ).setNameResourceId(R.string.hour_complication_name) + .setScreenReaderNameResourceId(R.string.hour_complication_name) + .setEnabled(false) + .build() + + val minuteComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = MINUTE_COMPLICATION_ID, + canvasComplicationFactory = invisibleComplicationFactory, + supportedTypes = listOf( ComplicationType.SHORT_TEXT, ComplicationType.MONOCHROMATIC_IMAGE, ComplicationType.SMALL_IMAGE + ), + defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( + SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT + ), + bounds = ComplicationSlotBounds( + RectF( + MINUTE_COMPLICATION_LEFT_BOUND, + MINUTE_COMPLICATION_TOP_BOUND, + MINUTE_COMPLICATION_RIGHT_BOUND, + MINUTE_COMPLICATION_BOTTOM_BOUND, + ) ) - ) - - object BottomRight : ComplicationConfig( - BOTTOM_RIGHT_COMPLICATION_ID, listOf( + ).setNameResourceId(R.string.minute_complication_name) + .setScreenReaderNameResourceId(R.string.minute_complication_name) + .setEnabled(false) + .build() + + val leftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = LEFT_COMPLICATION_ID, + canvasComplicationFactory = verticalComplicationFactory, + supportedTypes = listOf( ComplicationType.SHORT_TEXT, ComplicationType.MONOCHROMATIC_IMAGE, ComplicationType.SMALL_IMAGE - ) - ) - - object LeftIcon : ComplicationConfig( - LEFT_ICON_COMPLICATION_ID, listOf( - ComplicationType.MONOCHROMATIC_IMAGE, - ComplicationType.SMALL_IMAGE - ) - ) - - object RightIcon : ComplicationConfig( - RIGHT_ICON_COMPLICATION_ID, listOf( - ComplicationType.MONOCHROMATIC_IMAGE, - ComplicationType.SMALL_IMAGE - ) - ) - - object RightText : ComplicationConfig( - RIGHT_TEXT_COMPLICATION_ID, listOf( - ComplicationType.SHORT_TEXT, - ) - ) -} - -// Utility function that initializes default complication slots (left and right). -fun createComplicationSlotManager( - context: Context, - currentUserStyleRepository: CurrentUserStyleRepository, -): ComplicationSlotsManager { - - // TODO @rdnt setNameResourceId setScreenReaderNameResourceId use proper name for all - - val customLeftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.Left.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), - supportedTypes = ComplicationConfig.Left.supportedTypes, + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( LEFT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, + LEFT_COMPLICATION_TOP_BOUND, LEFT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + LEFT_COMPLICATION_BOTTOM_BOUND, ) ) ).setNameResourceId(R.string.left_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - - val customRightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.Right.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), - supportedTypes = ComplicationConfig.Right.supportedTypes, + .setScreenReaderNameResourceId(R.string.left_complication_name) + .setEnabled(false) + .build() + + val rightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = RIGHT_COMPLICATION_ID, + canvasComplicationFactory = verticalComplicationFactory, + supportedTypes = listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( RIGHT_COMPLICATION_LEFT_BOUND, - VERTICAL_COMPLICATION_TOP_BOUND, + RIGHT_COMPLICATION_TOP_BOUND, RIGHT_COMPLICATION_RIGHT_BOUND, - VERTICAL_COMPLICATION_BOTTOM_BOUND + RIGHT_COMPLICATION_BOTTOM_BOUND, ) ) ).setNameResourceId(R.string.right_complication_name) - .setScreenReaderNameResourceId(R.string.right_complication_name).setEnabled(false).build() - - val customTopComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.Top.id, - canvasComplicationFactory = createHorizontalComplicationFactory(context), - supportedTypes = ComplicationConfig.Top.supportedTypes, + .setScreenReaderNameResourceId(R.string.right_complication_name) + .setEnabled(false) + .build() + + val topComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = TOP_COMPLICATION_ID, + canvasComplicationFactory = horizontalComplicationFactory, + supportedTypes = listOf( + ComplicationType.SHORT_TEXT, + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.DATA_SOURCE_DATE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, + TOP_COMPLICATION_LEFT_BOUND, TOP_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, - TOP_COMPLICATION_BOTTOM_BOUND + TOP_COMPLICATION_RIGHT_BOUND, + TOP_COMPLICATION_BOTTOM_BOUND, ) ) ).setNameResourceId(R.string.top_complication_name) - .setScreenReaderNameResourceId(R.string.top_complication_name).setEnabled(false).build() - - val customBottomComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.Bottom.id, - canvasComplicationFactory = createHorizontalComplicationFactory(context), - supportedTypes = ComplicationConfig.Bottom.supportedTypes, + .setScreenReaderNameResourceId(R.string.top_complication_name) + .setEnabled(false) + .build() + + val bottomComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = BOTTOM_COMPLICATION_ID, + canvasComplicationFactory = horizontalComplicationFactory, + supportedTypes = listOf( + ComplicationType.SHORT_TEXT, + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.DATA_SOURCE_WATCH_BATTERY, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( - HORIZONTAL_COMPLICATION_LEFT_BOUND, + BOTTOM_COMPLICATION_LEFT_BOUND, BOTTOM_COMPLICATION_TOP_BOUND, - HORIZONTAL_COMPLICATION_RIGHT_BOUND, - BOTTOM_COMPLICATION_BOTTOM_BOUND + BOTTOM_COMPLICATION_RIGHT_BOUND, + BOTTOM_COMPLICATION_BOTTOM_BOUND, ) ) ).setNameResourceId(R.string.bottom_complication_name) - .setScreenReaderNameResourceId(R.string.bottom_complication_name).setEnabled(false).build() - - - val customTopLeftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.TopLeft.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), - supportedTypes = ComplicationConfig.TopLeft.supportedTypes, + .setScreenReaderNameResourceId(R.string.bottom_complication_name) + .setEnabled(false) + .build() + + val topLeftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = COMPLICATIONS_TOP_LEFT_COMPLICATION_ID, + canvasComplicationFactory = verticalComplicationFactory, + supportedTypes = listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( TOP_LEFT_COMPLICATION_LEFT_BOUND, - TOP_LEFT_COMPLICATION_TOP_BOUND, + TOP_LEFT_COMPLICATION_TOP_BOUND-6f/384f, TOP_LEFT_COMPLICATION_RIGHT_BOUND, - TOP_LEFT_COMPLICATION_BOTTOM_BOUND + TOP_LEFT_COMPLICATION_BOTTOM_BOUND+6f/384f, ) ) ).setNameResourceId(R.string.top_left_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - - val customBottomLeftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.BottomLeft.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), - supportedTypes = ComplicationConfig.BottomLeft.supportedTypes, + .setScreenReaderNameResourceId(R.string.top_left_complication_name) + .setEnabled(false) + .build() + + val bottomLeftComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID, + canvasComplicationFactory = verticalComplicationFactory, + supportedTypes = listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( BOTTOM_LEFT_COMPLICATION_LEFT_BOUND, - BOTTOM_LEFT_COMPLICATION_TOP_BOUND, + BOTTOM_LEFT_COMPLICATION_TOP_BOUND-6f/384f, BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND, - BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND + BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND+6f/384f, ) ) ).setNameResourceId(R.string.bottom_left_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - - val customTopRightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.TopRight.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), - supportedTypes = ComplicationConfig.TopRight.supportedTypes, + .setScreenReaderNameResourceId(R.string.bottom_left_complication_name) + .setEnabled(false) + .build() + + val topRightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = COMPLICATIONS_TOP_RIGHT_COMPLICATION_ID, + canvasComplicationFactory = verticalComplicationFactory, + supportedTypes = listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( TOP_RIGHT_COMPLICATION_LEFT_BOUND, - TOP_RIGHT_COMPLICATION_TOP_BOUND, + TOP_RIGHT_COMPLICATION_TOP_BOUND-6f/384f, TOP_RIGHT_COMPLICATION_RIGHT_BOUND, - TOP_RIGHT_COMPLICATION_BOTTOM_BOUND + TOP_RIGHT_COMPLICATION_BOTTOM_BOUND+6f/384f, ) ) ).setNameResourceId(R.string.top_right_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - - val customBottomRightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.BottomRight.id, - canvasComplicationFactory = createVerticalComplicationFactory(context), - supportedTypes = ComplicationConfig.BottomRight.supportedTypes, + .setScreenReaderNameResourceId(R.string.top_right_complication_name) + .setEnabled(false) + .build() + + val bottomRightComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = COMPLICATIONS_BOTTOM_RIGHT_COMPLICATION_ID, + canvasComplicationFactory = verticalComplicationFactory, + supportedTypes = listOf( + ComplicationType.SHORT_TEXT, + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), bounds = ComplicationSlotBounds( RectF( BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND, - BOTTOM_RIGHT_COMPLICATION_TOP_BOUND, + BOTTOM_RIGHT_COMPLICATION_TOP_BOUND-6f/384f, BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND, - BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND + BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND+6f/384f, ) ) ).setNameResourceId(R.string.bottom_right_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - - val customLeftIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.LeftIcon.id, - canvasComplicationFactory = createIconComplicationFactory(context), - supportedTypes = ComplicationConfig.LeftIcon.supportedTypes, + .setScreenReaderNameResourceId(R.string.bottom_right_complication_name) + .setEnabled(false) + .build() + + val leftIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = FOCUS_LEFT_ICON_COMPLICATION_ID, + canvasComplicationFactory = iconComplicationFactory, + supportedTypes = listOf( + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.MONOCHROMATIC_IMAGE ), @@ -385,13 +747,18 @@ fun createComplicationSlotManager( LEFT_ICON_COMPLICATION_BOTTOM_BOUND ) ) - ).setNameResourceId(R.string.left_icon_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - - val customRightIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.RightIcon.id, - canvasComplicationFactory = createIconComplicationFactory(context), - supportedTypes = ComplicationConfig.RightIcon.supportedTypes, + ).setNameResourceId(R.string.left_complication_name) + .setScreenReaderNameResourceId(R.string.left_complication_name) + .setEnabled(false) + .build() + + val rightIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = FOCUS_RIGHT_ICON_COMPLICATION_ID, + canvasComplicationFactory = iconComplicationFactory, + supportedTypes = listOf( + ComplicationType.MONOCHROMATIC_IMAGE, + ComplicationType.SMALL_IMAGE + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.MONOCHROMATIC_IMAGE ), @@ -403,13 +770,17 @@ fun createComplicationSlotManager( RIGHT_ICON_COMPLICATION_BOTTOM_BOUND ) ) - ).setNameResourceId(R.string.right_icon_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() - - val customRightTextComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( - id = ComplicationConfig.RightText.id, - canvasComplicationFactory = createHorizontalComplicationFactory(context), - supportedTypes = ComplicationConfig.RightText.supportedTypes, + ).setNameResourceId(R.string.right_complication_name) + .setScreenReaderNameResourceId(R.string.right_complication_name) + .setEnabled(false) + .build() + + val textComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( + id = RIGHT_TEXT_COMPLICATION_ID, + canvasComplicationFactory = horizontalComplicationFactory, + supportedTypes = listOf( + ComplicationType.SHORT_TEXT, + ), defaultDataSourcePolicy = DefaultComplicationDataSourcePolicy( SystemDataSources.NO_DATA_SOURCE, ComplicationType.SHORT_TEXT ), @@ -421,22 +792,31 @@ fun createComplicationSlotManager( RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND ) ) - ).setNameResourceId(R.string.right_text_complication_name) - .setScreenReaderNameResourceId(R.string.left_complication_name).setEnabled(false).build() + ).setNameResourceId(R.string.right_complication_name) + .setScreenReaderNameResourceId(R.string.right_complication_name) + .setEnabled(false) + .build() return ComplicationSlotsManager( listOf( - customLeftComplication, - customRightComplication, - customTopComplication, - customBottomComplication, - customTopLeftComplication, - customBottomLeftComplication, - customTopRightComplication, - customBottomRightComplication, - customLeftIconComplication, - customRightIconComplication, - customRightTextComplication + hourComplication, + minuteComplication, + + leftComplication, + rightComplication, + + topComplication, + bottomComplication, + + topLeftComplication, + bottomLeftComplication, + topRightComplication, + bottomRightComplication, + + leftIconComplication, + rightIconComplication, + + textComplication, ), currentUserStyleRepository ) } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index d45dcfc..02e8ed5 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -1,16 +1,24 @@ package dev.rdnt.m8face.utils import android.content.Context -import android.graphics.* -import android.util.Log +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.Rect +import android.graphics.RectF import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap -import androidx.core.graphics.withRotation import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters -import androidx.wear.watchface.complications.data.* +import androidx.wear.watchface.complications.data.ComplicationData +import androidx.wear.watchface.complications.data.ComplicationType +import androidx.wear.watchface.complications.data.NoDataComplicationData +import androidx.wear.watchface.complications.data.ShortTextComplicationData import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime @@ -77,7 +85,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication if (bounds.isEmpty) return canvas.drawRect(bounds, Paint().apply { - color = Color.parseColor("#11ffffff") + color = Color.parseColor("#22ffffff") }) when (data.type) { diff --git a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt index e6cc08c..bdcb6db 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt @@ -6,6 +6,7 @@ import android.util.Log import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.toRect import androidx.core.graphics.withRotation import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory @@ -48,7 +49,7 @@ class IconComplication(private val context: Context) : CanvasComplication { if (bounds.isEmpty) return canvas.drawRect(bounds, Paint().apply { - color = Color.parseColor("#11ffffff") + color = Color.parseColor("#22ffffff") }) when (data.type) { @@ -78,7 +79,7 @@ class IconComplication(private val context: Context) : CanvasComplication { val drawable = data.monochromaticImage.image.loadDrawable(context) ?: return - val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.8f).toInt() + val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.75f).toInt() icon = drawable.toBitmap(size, size) iconBounds = Rect(0, 0, size, size) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/InvisibleComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/InvisibleComplication.kt new file mode 100644 index 0000000..1357fcf --- /dev/null +++ b/app/src/main/java/dev/rdnt/m8face/utils/InvisibleComplication.kt @@ -0,0 +1,57 @@ +package dev.rdnt.m8face.utils + +import android.content.Context +import android.graphics.* +import android.util.Log +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.withRotation +import androidx.wear.watchface.CanvasComplication +import androidx.wear.watchface.CanvasComplicationFactory +import androidx.wear.watchface.RenderParameters +import androidx.wear.watchface.complications.data.* +import dev.rdnt.m8face.R +import java.time.Instant +import java.time.ZonedDateTime + +class InvisibleComplication(private val context: Context) : CanvasComplication { + override fun render( + canvas: Canvas, + bounds: Rect, + zonedDateTime: ZonedDateTime, + renderParameters: RenderParameters, + slotId: Int + ) { + if (bounds.isEmpty) return + + canvas.drawRect(bounds, Paint().apply { + color = Color.parseColor("#22ffffff") + }) + } + + override fun drawHighlight( + canvas: Canvas, + bounds: Rect, + boundsType: Int, + zonedDateTime: ZonedDateTime, + color: Int + ) { + // Rendering of highlights + } + + private var data: ComplicationData = NoDataComplicationData() + + override fun getData(): ComplicationData = data + + override fun loadData( + complicationData: ComplicationData, + loadDrawablesAsynchronous: Boolean + ) { + data = complicationData + } +} + +fun createInvisibleComplicationFactory(context: Context) = CanvasComplicationFactory { _, _ -> + InvisibleComplication(context) +} diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index 9a567ab..5e090d0 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -16,8 +16,11 @@ package dev.rdnt.m8face.utils import android.content.Context +import android.graphics.RectF import android.graphics.drawable.Icon import android.text.format.DateFormat +import androidx.wear.watchface.complications.ComplicationSlotBounds +import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.style.UserStyleSchema import androidx.wear.watchface.style.UserStyleSetting import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting @@ -50,135 +53,237 @@ const val BIG_AMBIENT_SETTING = "big_ambient_setting" fun createUserStyleSchema(context: Context): UserStyleSchema { // 1. Allows user to change the color styles of the watch face (if any are available). + val info1 = ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.INFO1.id), + resources = context.resources, + displayNameResourceId = R.string.info1_layout_style_name, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + HOUR_COMPLICATION_ID, + enabled = true, + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + MINUTE_COMPLICATION_ID, + enabled = true, + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ) + ) + + val info2 = ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.INFO2.id), + resources = context.resources, + displayNameResourceId = R.string.info2_layout_style_name, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + HOUR_COMPLICATION_ID, + enabled = true, + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + MINUTE_COMPLICATION_ID, + enabled = true, + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + RIGHT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ) + ) + + val info3 = ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.INFO3.id), + resources = context.resources, + displayNameResourceId = R.string.info3_layout_style_name, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + HOUR_COMPLICATION_ID, + enabled = true, + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + MINUTE_COMPLICATION_ID, + enabled = true, + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + COMPLICATIONS_TOP_LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID, + enabled = true + ), + ) + ) + + val info4 = ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.INFO4.id), + resources = context.resources, + displayNameResourceId = R.string.info4_layout_style_name, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + HOUR_COMPLICATION_ID, + enabled = true, + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + MINUTE_COMPLICATION_ID, + enabled = true, + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + COMPLICATIONS_TOP_LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + COMPLICATIONS_TOP_RIGHT_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + COMPLICATIONS_BOTTOM_RIGHT_COMPLICATION_ID, + enabled = true + ), + ) + ) + + val sport = ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.SPORT.id), + resources = context.resources, + displayNameResourceId = R.string.sport_layout_style_name, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + HOUR_COMPLICATION_ID, + enabled = true, + complicationSlotBounds = ComplicationSlotBounds(RectF( + HOUR_SPORT_COMPLICATION_LEFT_BOUND, + HOUR_SPORT_COMPLICATION_TOP_BOUND, + HOUR_SPORT_COMPLICATION_RIGHT_BOUND, + HOUR_SPORT_COMPLICATION_BOTTOM_BOUND, + )), + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + MINUTE_COMPLICATION_ID, + enabled = true, + complicationSlotBounds = ComplicationSlotBounds(RectF( + MINUTE_SPORT_COMPLICATION_LEFT_BOUND, + MINUTE_SPORT_COMPLICATION_TOP_BOUND, + MINUTE_SPORT_COMPLICATION_RIGHT_BOUND, + MINUTE_SPORT_COMPLICATION_BOTTOM_BOUND, + )), + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + TOP_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + BOTTOM_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + RIGHT_TEXT_COMPLICATION_ID, + enabled = true + ), + ) + ) + + val focus = ComplicationSlotsOption( + id = UserStyleSetting.Option.Id(LayoutStyle.FOCUS.id), + resources = context.resources, + displayNameResourceId = R.string.focus_layout_style_name, + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + complicationSlotOverlays = listOf( + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + HOUR_COMPLICATION_ID, + enabled = true, + complicationSlotBounds = ComplicationSlotBounds(RectF( + HOUR_FOCUS_COMPLICATION_LEFT_BOUND, + HOUR_FOCUS_COMPLICATION_TOP_BOUND, + HOUR_FOCUS_COMPLICATION_RIGHT_BOUND, + HOUR_FOCUS_COMPLICATION_BOTTOM_BOUND, + )), + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + MINUTE_COMPLICATION_ID, + enabled = true, + complicationSlotBounds = ComplicationSlotBounds(RectF( + MINUTE_FOCUS_COMPLICATION_LEFT_BOUND, + MINUTE_FOCUS_COMPLICATION_TOP_BOUND, + MINUTE_FOCUS_COMPLICATION_RIGHT_BOUND, + MINUTE_FOCUS_COMPLICATION_BOTTOM_BOUND, + )), + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + FOCUS_LEFT_ICON_COMPLICATION_ID, + enabled = true + ), + ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( + FOCUS_RIGHT_ICON_COMPLICATION_ID, + enabled = true + ), + ) + ) + val layoutStyleSetting = ComplicationSlotsUserStyleSetting( id = UserStyleSetting.Id(LAYOUT_STYLE_SETTING), resources = context.resources, displayNameResourceId = R.string.layout_style_setting, descriptionResourceId = R.string.layout_style_setting_description, - icon = Icon.createWithResource(context, R.drawable.mauve_style_icon), + icon = Icon.createWithResource(context, R.drawable.mauve_style_icon), // TODO: @rdnt fix icon complicationConfig = listOf( - ComplicationSlotsOption( - id = UserStyleSetting.Option.Id(LayoutStyle.DEFAULT.id), - resources = context.resources, - displayNameResourceId = R.string.default_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), - complicationSlotOverlays = listOf( - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - LEFT_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - RIGHT_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - TOP_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - BOTTOM_COMPLICATION_ID, - enabled = true - ), - ) - ), - - ComplicationSlotsOption( - id = UserStyleSetting.Option.Id(LayoutStyle.FOCUS.id), - resources = context.resources, - displayNameResourceId = R.string.focus_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), - complicationSlotOverlays = listOf( - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - LEFT_ICON_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - RIGHT_ICON_COMPLICATION_ID, - enabled = true - ), - ) - ), - - ComplicationSlotsOption( - id = UserStyleSetting.Option.Id(LayoutStyle.SPORT.id), - resources = context.resources, - displayNameResourceId = R.string.sport_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), - complicationSlotOverlays = listOf( - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - TOP_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - BOTTOM_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - RIGHT_TEXT_COMPLICATION_ID, - enabled = true - ), - ) - ), - - ComplicationSlotsOption( - id = UserStyleSetting.Option.Id(LayoutStyle.COMPLICATIONS.id), - resources = context.resources, - displayNameResourceId = R.string.complications_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), - complicationSlotOverlays = listOf( - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - TOP_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - BOTTOM_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - TOP_LEFT_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - BOTTOM_LEFT_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - TOP_RIGHT_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - BOTTOM_RIGHT_COMPLICATION_ID, - enabled = true - ), - ) - ), + info1, + info2, + info3, + info4, + sport, + focus, ), listOf(WatchFaceLayer.COMPLICATIONS), - defaultOption = ComplicationSlotsOption( - id = UserStyleSetting.Option.Id(LayoutStyle.DEFAULT.id), - resources = context.resources, - displayNameResourceId = R.string.default_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), - complicationSlotOverlays = listOf( - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - LEFT_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - RIGHT_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - TOP_COMPLICATION_ID, - enabled = true - ), - ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( - BOTTOM_COMPLICATION_ID, - enabled = true - ), - ) - ), + defaultOption = info2, ) val colorStyleSetting = diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index a67f3f0..f52ad5b 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -6,6 +6,7 @@ import android.util.Log import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.toRect import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters @@ -78,7 +79,7 @@ class VerticalComplication(private val context: Context) : CanvasComplication { if (bounds.isEmpty) return canvas.drawRect(bounds, Paint().apply { - color = Color.parseColor("#11ffffff") + color = Color.parseColor("#22ffffff") }) when (data.type) { diff --git a/app/src/main/res/drawable-nodpi/bold_outline_style_icon.png b/app/src/main/res/drawable-nodpi/bold_outline_style_icon.png index d3ba1205e7f4a951b0dc315267ca94b240444ce1..b9c45421d0204c1c34ba77939478e99c890292d0 100644 GIT binary patch delta 413 zcmV;O0b>578>JhNB!7izLqkwWLqi~Na&Km7Y-IodD3N`UJxIe)6opSyr6LtUi-noci*4Yr{qlr_(bA4rW+RV2Jy_MrE}gV4zZG?5T6rI7<576N3P2*zi}=)Ebz>b zkx9)Hhls^u2g@DIN`^{2O&nHKjq-)8%L?Z$&T6H`TKD8H4Cb|!G}md3B90{_kc0>s zHIz|-g$V5$DSswXbRP5Yk2wA$xny#c!N{?IDpW|0AN&t~&(9iCZTtJ!wwot_{~5T_+Wu+-nEfQZ-qylLK>s#yaoyJBJ>YT&7=F?vLvkcPEul~V z-p}Zpa=_p%(7Wc&t#yvm2OvYUTD}1e4uSC^Wv_d@yAh{zZvXbQ=Jx}7Y;vj!yFW{_ Hu>tc7nNh?V delta 412 zcmV;N0b~B98>AbMB!7fyLqkwWLqi~Na&Km7Y-IodD3N`UJxIeq9K~N#r6LtUJBW11 zP@ODD6>*d*7QsSkE41oha_JW|X-HCB90k{cgCC1k2N!2u9b5%L@B_rv#YxdcO8j3^ zXc6PVaX;SOd)&PPgl3hgX3r#`YL<~s#Kl~GRSdjBKo~=aA%7q-Q%`0Vv+x{W_we!c zF3PjK&;2e3JS*_Mt`=0!T!GgAu;X2JQ5?DeKDTt6! zM+H?_h|;Q&Vt*n{`*9EdsN+wOOD0zZj2sK7L51Y_!T;cQw`OrF<|YN>K<|rfe~bZv zU7*#l?eAmTZk+)BXW&Zf_-jpI_LKBRM~fT*gWJHxbw`u;fXf|VDots + Hours + Minutes + Left Right + Top Bottom + Top Left Bottom Left Top Right Bottom Right - Left - Right - Right - Default - Seconds + + Info I + Info II + Info III + Info IV Focus Sport - Complications From 0551110d3d018a0a66e9d8860aed9822d131fb0e Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sat, 10 Feb 2024 23:41:46 +0200 Subject: [PATCH 10/52] wip --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 381 +++++++++--------- 1 file changed, 193 insertions(+), 188 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index b24d2c0..84841cc 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -230,6 +230,7 @@ class WatchCanvasRenderer( private val ambientTransitionMs = 1000L private var drawProperties = DrawProperties() + private var isHeadless = false private val ambientExitAnimator = AnimatorSet().apply { @@ -289,6 +290,7 @@ class WatchCanvasRenderer( coroutineScope.launch { watchState.isAmbient.collect { isAmbient -> + isHeadless = watchState.isHeadless if (!watchState.isHeadless) { if (isAmbient!!) { // you call this readable? come on ambientExitAnimator.cancel() @@ -298,13 +300,18 @@ class WatchCanvasRenderer( ambientExitAnimator.start() } } else { - if (isAmbient!!) { // you call this readable? come on - ambientExitAnimator.setupStartValues() - drawProperties.timeScale = 0f - } else { - ambientExitAnimator.setupEndValues() - drawProperties.timeScale = 1f - } + ambientExitAnimator.setupStartValues() + drawProperties.timeScale = 0f +// ambientExitAnimator.setupEndValues() +// drawProperties.timeScale = 1f + +// if (isAmbient!!) { // you call this readable? come on +// ambientExitAnimator.setupStartValues() +// drawProperties.timeScale = 0f +// } else { +// ambientExitAnimator.setupEndValues() +// drawProperties.timeScale = 1f +// } } } } @@ -513,107 +520,112 @@ class WatchCanvasRenderer( } } - override fun render( - canvas: Canvas, - bounds: Rect, - zonedDateTime: ZonedDateTime, - sharedAssets: AnalogSharedAssets, - ) { - updateRefreshRate() +// val scaleOffset = if (this.watchFaceData.bigAmbient) { +// 18f / 14f - 1f +// } else { +// 16f / 14f - 1f +// } - canvas.drawColor(Color.parseColor("#ff000000")) + private val scale = if (!isHeadless) drawProperties.timeScale else 1f +//private val scale = 1f - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - var hour: Int - if (is24Format) { - hour = zonedDateTime.hour - } else { - hour = zonedDateTime.hour % 12 - if (hour == 0) { - hour = 12 - } + val timeTextSize: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 18f; } - val timeTextSize = fun(): Float { - return when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 18f; - } + else -> { + 14f; + } + } * scale - else -> { - 14f; - } - } - }() + val hourOffsetX: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + 81f; + } - val hourOffsetX = fun(): Float { - return when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> { - 81f; - } + LayoutStyle.FOCUS.id -> { + 93f; + } - LayoutStyle.FOCUS.id -> { - 93f; - } + else -> { + 115f; + } + } * scale - else -> { - 115f; - } - } - }() + val hourOffsetY: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 183f; + } - val hourOffsetY = fun(): Float { - return when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 183f; - } + else -> { + 185f; + } + } * scale - else -> { - 185f; - } - } - }() + val minuteOffsetX: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + 81f + } - val minuteOffsetX = fun(): (Float) { - return when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> { - 81f - } + LayoutStyle.FOCUS.id -> { + 93f; + } - LayoutStyle.FOCUS.id -> { - 93f; - } + else -> { + 115f; + } + } * scale - else -> { - 115f; - } - } - }() + val minuteOffsetY: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 327f + } - val minuteOffsetY = fun(): (Float) { - return when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 327f - } + else -> { + 297f; + } + } * scale - else -> { - 297f; - } - } - }() + fun getHour(zonedDateTime: ZonedDateTime): Int { + if (is24Format) { + return zonedDateTime.hour + } else { + val hour = zonedDateTime.hour % 12 + if (hour == 0) { + return 12 + } + return hour + } + } + + override fun render( + canvas: Canvas, + bounds: Rect, + zonedDateTime: ZonedDateTime, + sharedAssets: AnalogSharedAssets, + ) { + updateRefreshRate() + canvas.drawColor(Color.parseColor("#ff000000")) - drawTime2( + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { + drawTime( canvas, bounds, - hour, + getHour(zonedDateTime), hourPaint, hourOffsetX, hourOffsetY, timeTextSize ) - drawTime2( + drawTime( canvas, bounds, zonedDateTime.minute, @@ -623,117 +635,110 @@ class WatchCanvasRenderer( timeTextSize ) - -// var hour: Int -// if (is24Format) { -// hour = zonedDateTime.hour +// if (drawProperties.timeScale == 0f) { +// var hourOffsetX = 0f +// var hourOffsetY = 0f +// var minuteOffsetX = 0f +// var minuteOffsetY = 0f +// +// when (watchFaceData.ambientStyle.id) { +// AmbientStyle.OUTLINE.id -> { +// if (watchFaceData.bigAmbient) { +// hourOffsetX = -99f +// hourOffsetY = -9f +// minuteOffsetX = -99f +// minuteOffsetY = 135f +// } else { +// hourOffsetX = -88f +// hourOffsetY = -8f +// minuteOffsetX = -88f +// minuteOffsetY = 120f +// } +// } +// +// AmbientStyle.BOLD_OUTLINE.id -> { +// if (watchFaceData.bigAmbient) { +// hourOffsetX = -99f +// hourOffsetY = -9f +// minuteOffsetX = -99f +// minuteOffsetY = 135f +// } else { +// hourOffsetX = -88f +// hourOffsetY = -8f +// minuteOffsetX = -88f +// minuteOffsetY = 120f +// } +// } +// +// AmbientStyle.FILLED.id -> { +// if (watchFaceData.bigAmbient) { +// hourOffsetX = -99f +// hourOffsetY = -9f +// minuteOffsetX = -99f +// minuteOffsetY = 135f +// } else { +// hourOffsetX = -88f +// hourOffsetY = -8f +// minuteOffsetX = -88f +// minuteOffsetY = 120f +// } +// } +// } +// +//// drawTimeOld(canvas, bounds, hour, ambientHourPaint, hourOffsetX, hourOffsetY, 0f) +//// drawTimeOld( +//// canvas, +//// bounds, +//// zonedDateTime.minute, +//// ambientMinutePaint, +//// minuteOffsetX, +//// minuteOffsetY, +//// 0f +//// ) // } else { -// hour = zonedDateTime.hour % 12 -// if (hour == 0) { -// hour = 12 +// when (watchFaceData.secondsStyle.id) { +// SecondsStyle.NONE.id -> { +// +// } +// +// SecondsStyle.DASHES.id -> { +// drawDashes(canvas, bounds, zonedDateTime) +// } +// +// SecondsStyle.DOTS.id -> { +// drawDots(canvas, bounds, zonedDateTime) +// } // } +// +// val scaleOffset = if (this.watchFaceData.bigAmbient) { +// 18f / 14f - 1f +// } else { +// 16f / 14f - 1f +// } +// +//// drawTimeOld(canvas, bounds, hour, hourPaint, -77f, -7f, scaleOffset) // Rect(0, 0, 152, 14)) +//// drawTimeOld( +//// canvas, +//// bounds, +//// zonedDateTime.minute, +//// minutePaint, +//// -77f, +//// 105f, +//// scaleOffset +//// )//Rect(0, 0, 152, -210)) // } - if (drawProperties.timeScale == 0f) { - var hourOffsetX = 0f - var hourOffsetY = 0f - var minuteOffsetX = 0f - var minuteOffsetY = 0f - - when (watchFaceData.ambientStyle.id) { - AmbientStyle.OUTLINE.id -> { - if (watchFaceData.bigAmbient) { - hourOffsetX = -99f - hourOffsetY = -9f - minuteOffsetX = -99f - minuteOffsetY = 135f - } else { - hourOffsetX = -88f - hourOffsetY = -8f - minuteOffsetX = -88f - minuteOffsetY = 120f - } - } - - AmbientStyle.BOLD_OUTLINE.id -> { - if (watchFaceData.bigAmbient) { - hourOffsetX = -99f - hourOffsetY = -9f - minuteOffsetX = -99f - minuteOffsetY = 135f - } else { - hourOffsetX = -88f - hourOffsetY = -8f - minuteOffsetX = -88f - minuteOffsetY = 120f - } - } - - AmbientStyle.FILLED.id -> { - if (watchFaceData.bigAmbient) { - hourOffsetX = -99f - hourOffsetY = -9f - minuteOffsetX = -99f - minuteOffsetY = 135f - } else { - hourOffsetX = -88f - hourOffsetY = -8f - minuteOffsetX = -88f - minuteOffsetY = 120f - } - } - } - - drawTime(canvas, bounds, hour, ambientHourPaint, hourOffsetX, hourOffsetY, 0f) - drawTime( - canvas, - bounds, - zonedDateTime.minute, - ambientMinutePaint, - minuteOffsetX, - minuteOffsetY, - 0f - ) - } else { - when (watchFaceData.secondsStyle.id) { - SecondsStyle.NONE.id -> { - - } - - SecondsStyle.DASHES.id -> { - drawDashes(canvas, bounds, zonedDateTime) - } - - SecondsStyle.DOTS.id -> { - drawDots(canvas, bounds, zonedDateTime) - } - } - - val scaleOffset = if (this.watchFaceData.bigAmbient) { - 18f / 14f - 1f - } else { - 16f / 14f - 1f - } - - drawTime(canvas, bounds, hour, hourPaint, -77f, -7f, scaleOffset) // Rect(0, 0, 152, 14)) - drawTime( - canvas, - bounds, - zonedDateTime.minute, - minutePaint, - -77f, - 105f, - scaleOffset - )//Rect(0, 0, 152, -210)) - } - } - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - drawProperties.timeScale != 0f - ) { + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS)) { drawComplications(canvas, zonedDateTime) } + +// if (renderParameters.watchFaceLayesr.contains(WatchFaceLayer.COMPLICATIONS) && +// drawProperties.timeScale != 0f +// ) { +// drawComplications(canvas, zonedDateTime) +// } } override fun shouldAnimate(): Boolean { @@ -768,7 +773,7 @@ class WatchCanvasRenderer( } } - private fun drawTime2( + private fun drawTime( canvas: Canvas, bounds: Rect, time: Int, @@ -793,7 +798,7 @@ class WatchCanvasRenderer( } } - private fun drawTime( + private fun drawTimeOld( canvas: Canvas, bounds: Rect, time: Int, From 675d416788f17333f62b222ef0f029b586138cdc Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 11 Feb 2024 17:21:20 +0200 Subject: [PATCH 11/52] Cleaned up time rendering (wip) --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 383 +++++++++++++++--- .../m8face/data/watchface/WatchFaceData.kt | 1 + .../m8face/editor/WatchFaceConfigActivity.kt | 48 ++- .../editor/WatchFaceConfigStateHolder.kt | 33 +- .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 12 + app/src/main/res/values/strings.xml | 2 + 6 files changed, 418 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 84841cc..b44f804 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -20,19 +20,24 @@ import android.animation.Keyframe import android.animation.ObjectAnimator import android.animation.PropertyValuesHolder import android.content.Context -import android.graphics.* +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.RectF import android.util.FloatProperty import android.util.Log import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep -import androidx.core.content.ContextCompat.getDrawable import androidx.core.graphics.ColorUtils -import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.withRotation import androidx.core.graphics.withScale +import androidx.core.graphics.withTranslation import androidx.wear.watchface.ComplicationSlotsManager -import androidx.wear.watchface.DrawMode import androidx.wear.watchface.Renderer import androidx.wear.watchface.WatchState import androidx.wear.watchface.style.CurrentUserStyleRepository @@ -48,9 +53,9 @@ import dev.rdnt.m8face.data.watchface.WatchFaceData import dev.rdnt.m8face.utils.AMBIENT_STYLE_SETTING import dev.rdnt.m8face.utils.BIG_AMBIENT_SETTING import dev.rdnt.m8face.utils.COLOR_STYLE_SETTING +import dev.rdnt.m8face.utils.DETAILED_AMBIENT_SETTING import dev.rdnt.m8face.utils.HorizontalComplication import dev.rdnt.m8face.utils.IconComplication -import dev.rdnt.m8face.utils.InvisibleComplication import dev.rdnt.m8face.utils.LAYOUT_STYLE_SETTING import dev.rdnt.m8face.utils.MILITARY_TIME_SETTING import dev.rdnt.m8face.utils.SECONDS_STYLE_SETTING @@ -59,7 +64,12 @@ import java.time.Duration import java.time.ZonedDateTime import kotlin.math.pow import kotlin.math.sqrt -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch + // Default for how long each frame is displayed at expected frame rate. private const val DEFAULT_INTERACTIVE_DRAW_MODE_UPDATE_DELAY_MILLIS: Long = 16 @@ -133,6 +143,7 @@ class WatchCanvasRenderer( } private val hourPaint = Paint().apply { + this.isSubpixelText = true isAntiAlias = true // make sure text is not anti-aliased even with this on typeface = context.resources.getFont(R.font.m8stealth57) // textSize = 112f / 14f * 14f // TODO: 98f/112f @@ -231,6 +242,7 @@ class WatchCanvasRenderer( private val ambientTransitionMs = 1000L private var drawProperties = DrawProperties() private var isHeadless = false + private var isAmbient = false private val ambientExitAnimator = AnimatorSet().apply { @@ -289,12 +301,16 @@ class WatchCanvasRenderer( } coroutineScope.launch { - watchState.isAmbient.collect { isAmbient -> + watchState.isAmbient.collect { ambient -> isHeadless = watchState.isHeadless + isAmbient = ambient!! + if (!watchState.isHeadless) { - if (isAmbient!!) { // you call this readable? come on - ambientExitAnimator.cancel() - drawProperties.timeScale = 0f + if (isAmbient) { + ambientEnterAnimator.setupStartValues() + ambientEnterAnimator.start() +// ambientExitAnimator.cancel() +// drawProperties.timeScale = 0f } else { ambientExitAnimator.setupStartValues() ambientExitAnimator.start() @@ -302,16 +318,6 @@ class WatchCanvasRenderer( } else { ambientExitAnimator.setupStartValues() drawProperties.timeScale = 0f -// ambientExitAnimator.setupEndValues() -// drawProperties.timeScale = 1f - -// if (isAmbient!!) { // you call this readable? come on -// ambientExitAnimator.setupStartValues() -// drawProperties.timeScale = 0f -// } else { -// ambientExitAnimator.setupEndValues() -// drawProperties.timeScale = 1f -// } } } } @@ -404,6 +410,15 @@ class WatchCanvasRenderer( bigAmbient = booleanValue.value, ) } + + DETAILED_AMBIENT_SETTING -> { + val booleanValue = options.value as + UserStyleSetting.BooleanUserStyleSetting.BooleanOption + + newWatchFaceData = newWatchFaceData.copy( + detailedAmbient = booleanValue.value, + ) + } } } @@ -526,8 +541,12 @@ class WatchCanvasRenderer( // 16f / 14f - 1f // } - private val scale = if (!isHeadless) drawProperties.timeScale else 1f -//private val scale = 1f + private val scale + get() = if (!isHeadless) drawProperties.timeScale else 1f + + fun interpolate(start: Float, end: Float): Float { + return start + scale * (end - start) + } val timeTextSize: Float get() = when (watchFaceData.layoutStyle.id) { @@ -536,24 +555,38 @@ class WatchCanvasRenderer( } else -> { - 14f; + if (watchFaceData.detailedAmbient) { + 14f + } else { + interpolate(18f, 14f) + } + +// 14f + (1f - scale) * (18f - 14f); } - } * scale + } val hourOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { - 81f; - } +// 81f; + if (watchFaceData.detailedAmbient) { + -34f + } else { + interpolate(0f, -34f) + } - LayoutStyle.FOCUS.id -> { - 93f; } +// LayoutStyle.FOCUS.id -> { +//// 93f; +// 0f +// } + else -> { - 115f; + 0f +// interpolate(93f, 115f) } - } * scale + } val hourOffsetY: Float get() = when (watchFaceData.layoutStyle.id) { @@ -563,8 +596,9 @@ class WatchCanvasRenderer( else -> { 185f; +// interpolate(183f, 185f) } - } * scale + } val minuteOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { @@ -577,9 +611,10 @@ class WatchCanvasRenderer( } else -> { - 115f; +// 115f; + interpolate(93f, 115f) } - } * scale + } val minuteOffsetY: Float get() = when (watchFaceData.layoutStyle.id) { @@ -589,8 +624,9 @@ class WatchCanvasRenderer( else -> { 297f; +// interpolate(327f, 297f) } - } * scale + } fun getHour(zonedDateTime: ZonedDateTime): Int { if (is24Format) { @@ -622,18 +658,20 @@ class WatchCanvasRenderer( hourPaint, hourOffsetX, hourOffsetY, - timeTextSize + timeTextSize, +// scale, ) - drawTime( - canvas, - bounds, - zonedDateTime.minute, - hourPaint, - minuteOffsetX, - minuteOffsetY, - timeTextSize - ) +// drawTime( +// canvas, +// bounds, +// zonedDateTime.minute, +// hourPaint, +// minuteOffsetX, +// minuteOffsetY, +// timeTextSize, +//// scale, +// ) // if (drawProperties.timeScale == 0f) { // var hourOffsetX = 0f @@ -730,7 +768,9 @@ class WatchCanvasRenderer( } - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS)) { + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && + watchFaceData.detailedAmbient || drawProperties.timeScale != 0f + ) { drawComplications(canvas, zonedDateTime) } @@ -751,7 +791,7 @@ class WatchCanvasRenderer( // ----- All drawing functions ----- private fun drawComplications(canvas: Canvas, zonedDateTime: ZonedDateTime) { - val opacity = this.easeInOutCirc(drawProperties.timeScale) + val opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) for ((_, complication) in complicationSlotsManager.complicationSlots) { if (complication.enabled) { @@ -782,20 +822,238 @@ class WatchCanvasRenderer( offsetY: Float, textSize: Float ) { + +// val offsetX = -34f + val text = time.toString().padStart(2, '0') + + val p = Paint(paint) + p.textSize *= 14f + p.isAntiAlias = true + p.isDither = true + p.isFilterBitmap = true + +// val textBounds = Rect() +// p.getTextBounds(text, 0, text.length, textBounds) + + var scale = 1f + scale *= textSize / 14f + +// canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { +// canvas.drawText( +// text, +//// 192f-textBounds.width().toFloat()/2f, 192f - textBounds.height().toFloat()/2f, +// 192f-textBounds.width().toFloat()/2f, 192f-7f, +//// offsetX, +//// offsetY, +// p +// ) +// } + + ///////////////////////// + + val cacheBitmap = Bitmap.createBitmap( + bounds.width(), + bounds.height(), + Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(cacheBitmap) +// val bitmapPaint = Paint() +// bitmapPaint.textSize = 14f + + + val textBounds = Rect() + p.getTextBounds(text, 0, text.length, textBounds) + + bitmapCanvas.drawText( + text, + 192f-textBounds.width().toFloat()/2f, + 192f+7f+textBounds.height(), + p, + ) + + Log.d("@@@", "$offsetX") + + canvas.withTranslation(offsetX, 0f) { + canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.drawBitmap( + cacheBitmap, + 0f, + 0f, + Paint(), + ) + } + } + + + /////////////////////// + + val cacheBitmap2 = Bitmap.createBitmap( + bounds.width(), + bounds.height(), + Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas2 = Canvas(cacheBitmap2) +// val bitmapPaint = Paint() +// bitmapPaint.textSize = 14f + + + val textBounds2 = Rect() + p.getTextBounds(text, 0, text.length, textBounds2) + + bitmapCanvas2.drawText( + text, + 192f-textBounds2.width().toFloat()/2f, + 192f-7f, + p, + ) + + canvas.withTranslation(offsetX, 0f) { + canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.drawBitmap( + cacheBitmap2, + 0f, + 0f, + Paint(), + ) + } + } + + } + + private fun drawTimeBitmap( + canvas: Canvas, + bounds: Rect, + time: Int, + paint: Paint, + offsetX: Float, + offsetY: Float, + textSize: Float, +// timeScale: Float + ) { val p = Paint(paint) // p.textSize = p.textSize // / 384F * bounds.width() - p.textSize *= textSize + p.textSize = 14f*8f + p.isSubpixelText = true + p.isAntiAlias = true +// p.textSize = 14f + + var scale = 1f - val scale = 1f + scale = scale * (textSize/14f) + + val text = time.toString().padStart(2, '0') + + val textBounds = Rect() + p.getTextBounds(text, 0, text.length, textBounds) + + + val cacheBitmap = Bitmap.createBitmap( + 384, + 384, + Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(cacheBitmap) + + val bitmapPaint = Paint(paint) + bitmapPaint.textSize *= 14f +// bitmapPaint.color = Color.WHITE +// bitmapPaint.isAntiAlias = true +// bitmapPaint.isDither = true +// bitmapPaint.isFilterBitmap = true + + +// bitmapCanvas.drawText(text, 0f, 0f, bitmapPaint) + + bitmapCanvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { + bitmapCanvas.drawText(text, 192f, 192f, bitmapPaint) + } + + + +// val resources: Resources = context.resources +// val density = resources.displayMetrics.density +//// var bitmap = BitmapFactory.decodeResource(resources, 0) +// +// var bitmap = Bitmap.createBitmap(textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888); +// +// +// val bitmapConfig = bitmap.config +// +// // set default bitmap config if none +//// if(bitmapConfig == null) { +//// bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888; +//// } +//// val drawable = data.smallImage.image.loadDrawable(context) ?: return +// +// +// bitmap = bitmap.copy(bitmapConfig, true) +// +//// val bitmapCanvas = Canvas(bitmap) +// +// val x = (bitmap.width - textBounds.width()) / 2 +// val y = (bitmap.height + textBounds.height()) / 2 +// +// +// val paint2 = Paint(Paint.ANTI_ALIAS_FLAG) +// // text color - #3D3D3D +// // text color - #3D3D3D +// paint2.color = Color.rgb(61, 61, 61) +// // text size in pixels +// // text size in pixels +// paint2.textSize = (14 * scale).toInt().toFloat() +// // text shadow +// // text shadow +// paint2.setShadowLayer(1f, 0f, 1f, Color.WHITE) +// +// bitmapCanvas.drawText(text, 0f, 0f, paint2) + + +// iconBounds = Rect(0, 0, 0, size) + +// val dstRect = RectF( +// bounds.exactCenterX() - textBounds.width() / 2, +// bounds.exactCenterY() - textBounds.height() / 2, +// bounds.exactCenterX() + textBounds.width() / 2, +// bounds.exactCenterY() + textBounds.height() / 2, + val imgPaint = Paint() +// imgPaint.color = Color.WHITE +// imgPaint.isAntiAlias = true +// imgPaint.isDither = true +// imgPaint.isFilterBitmap = true + + val colorMatrix = ColorMatrix() + colorMatrix.setSaturation(0f) + val filter = ColorMatrixColorFilter(colorMatrix) + imgPaint.colorFilter = filter + +// p.isDither = true +// p.isFilterBitmap = true +// p.isAntiAlias = true + +// ) canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawText( - time.toString().padStart(2, '0'), - offsetX, - offsetY, - p - ) + canvas.drawText(text, 192f, 192f, p) + } + + canvas.withTranslation(0f, 0f) { +// canvas.drawBitmap(cacheBitmap, 0f, 0f, imgPaint) +// canvas.drawText(text, 192f, 192f, p) } +// canvas.withTranslation(offsetX, offsetY) { + +// canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { +// canvas.withTranslation(-77f, -7f) { +// canvas.drawText( +// time.toString().padStart(2, '0'), +// 192f, 192f, +// // offsetX, +// // offsetY, +// p +// ) +// } +// } +// } } private fun drawTimeOld( @@ -1152,3 +1410,20 @@ class WatchCanvasRenderer( private const val TAG = "WatchCanvasRenderer" } } + +fun bitmapCache(canvas: Canvas, bounds: Rect) { + val cacheBitmap = Bitmap.createBitmap( + bounds.width(), + bounds.height(), + Bitmap.Config.ARGB_8888 + ) + val text = "example" + val bitmapCanvas = Canvas(cacheBitmap) + val bitmapPaint = Paint() + bitmapPaint.textSize = 14f + bitmapCanvas.drawText(text, 192f+10f, 192f, bitmapPaint) + + canvas.withScale(1f, 1f, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.drawBitmap(cacheBitmap, 0f, 0f, Paint()) + } +} diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt index e047797..7dd0133 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt @@ -25,4 +25,5 @@ data class WatchFaceData( val secondsStyle: SecondsStyle = SecondsStyle.NONE, val militaryTime: Boolean = true, val bigAmbient: Boolean = true, + val detailedAmbient: Boolean = false, ) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index 9701741..a8b14b8 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -348,6 +348,7 @@ fun WatchfaceConfigApp( secondsStyles.indexOfFirst { it.id == state.userStylesAndPreview.secondsStyleId } val militaryTimeEnabled = state.userStylesAndPreview.militaryTime val bigAmbientEnabled = state.userStylesAndPreview.bigAmbient + val detailedAmbientEnabled = state.userStylesAndPreview.detailedAmbient Box( Modifier @@ -368,6 +369,7 @@ fun WatchfaceConfigApp( secondsStyleIndex, militaryTimeEnabled, bigAmbientEnabled, + detailedAmbientEnabled, ) } } @@ -402,6 +404,7 @@ fun ConfigScaffold( secondsStyleIndex: Int, militaryTimeEnabled: Boolean, bigAmbientEnabled: Boolean, + detailedAmbientEnabled: Boolean, ) { Log.d( "Editor", @@ -460,7 +463,7 @@ fun ConfigScaffold( 2 -> SecondsStyleSelect(focusRequester2, stateHolder, secondsStyles, secondsStyleIndex) 3 -> AmbientStyleSelect(focusRequester3, stateHolder, ambientStyles, ambientStyleIndex) 4 -> ComplicationPicker(stateHolder, layoutIndex) - 5 -> Options(stateHolder, militaryTimeEnabled, bigAmbientEnabled) + 5 -> Options(stateHolder, militaryTimeEnabled, bigAmbientEnabled, detailedAmbientEnabled) } } @@ -673,6 +676,7 @@ fun Options( stateHolder: WatchFaceConfigStateHolder, militaryTime: Boolean, bigAmbient: Boolean, + detailedAmbient: Boolean, ) { Box( Modifier @@ -707,7 +711,7 @@ fun Options( label = { Text( text = "Military Time", - fontSize = 14.sp, + fontSize = 12.sp, fontWeight = Medium, fontFamily = Default, color = White @@ -717,9 +721,9 @@ fun Options( ToggleChip( modifier = Modifier - .fillMaxWidth() - .padding(12.dp, 0.dp) - .height((40.dp)), + .fillMaxWidth() + .padding(12.dp, 0.dp) + .height((40.dp)), checked = bigAmbient, colors = ToggleChipDefaults.toggleChipColors( checkedStartBackgroundColor = Transparent, @@ -739,7 +743,39 @@ fun Options( label = { Text( text = "Big Ambient", - fontSize = 14.sp, + fontSize = 12.sp, + fontWeight = Medium, + fontFamily = Default, + color = White + ) + }, + ) + + ToggleChip( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp, 0.dp) + .height((40.dp)), + checked = detailedAmbient, + colors = ToggleChipDefaults.toggleChipColors( + checkedStartBackgroundColor = Transparent, + checkedEndBackgroundColor = Transparent, + uncheckedStartBackgroundColor = Transparent, + uncheckedEndBackgroundColor = Transparent, + ), + onCheckedChange = { + stateHolder.setDetailedAmbient(it) + }, + toggleControl = { + Switch( + modifier = Modifier.padding(0.dp), + checked = detailedAmbient, + ) + }, + label = { + Text( + text = "Detailed Ambient", + fontSize = 12.sp, fontWeight = Medium, fontFamily = Default, color = White diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index 20222f0..47140f2 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -66,6 +66,7 @@ class WatchFaceConfigStateHolder( private lateinit var secondsStyleKey: UserStyleSetting.ListUserStyleSetting private lateinit var militaryTimeKey: UserStyleSetting.BooleanUserStyleSetting private lateinit var bigAmbientKey: UserStyleSetting.BooleanUserStyleSetting + private lateinit var detailedAmbientKey: UserStyleSetting.BooleanUserStyleSetting val uiState: StateFlow = flow { @@ -123,6 +124,10 @@ class WatchFaceConfigStateHolder( BIG_AMBIENT_SETTING -> { bigAmbientKey = setting as UserStyleSetting.BooleanUserStyleSetting } + + DETAILED_AMBIENT_SETTING -> { + detailedAmbientKey = setting as UserStyleSetting.BooleanUserStyleSetting + } } } } @@ -168,6 +173,9 @@ class WatchFaceConfigStateHolder( val bigAmbient = userStyle[bigAmbientKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption + val detailedAmbient = + userStyle[detailedAmbientKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption + return UserStylesAndPreview( layoutStyleId = layoutStyle.id.toString(), colorStyleId = colorStyle.id.toString(), @@ -175,6 +183,7 @@ class WatchFaceConfigStateHolder( secondsStyleId = secondsStyle.id.toString(), militaryTime = militaryTime.value, bigAmbient = bigAmbient.value, + detailedAmbient = detailedAmbient.value, previewImage = bitmap, ) } @@ -350,6 +359,27 @@ class WatchFaceConfigStateHolder( bigAmbientKey, UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(enabled) ) + + if (enabled) { + setUserStyleOption( + detailedAmbientKey, + UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(false) + ) + } + } + + fun setDetailedAmbient(enabled: Boolean) { + setUserStyleOption( + detailedAmbientKey, + UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(enabled) + ) + + if (enabled) { + setUserStyleOption( + bigAmbientKey, + UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(false) + ) + } } // Saves User Style Option change back to the back to the EditorSession. @@ -384,7 +414,8 @@ class WatchFaceConfigStateHolder( val secondsStyleId: String, val militaryTime: Boolean, val bigAmbient: Boolean, - val previewImage: Bitmap + val detailedAmbient: Boolean, + val previewImage: Bitmap, ) companion object { diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index 5e090d0..4106979 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -44,6 +44,7 @@ const val AMBIENT_STYLE_SETTING = "ambient_style_setting" const val SECONDS_STYLE_SETTING = "seconds_style_setting" const val MILITARY_TIME_SETTING = "military_time_setting" const val BIG_AMBIENT_SETTING = "big_ambient_setting" +const val DETAILED_AMBIENT_SETTING = "detailed_ambient_setting" /* * Creates user styles in the settings activity associated with the watch face, so users can @@ -343,6 +344,16 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { false, ) + val detailedAmbientSetting = UserStyleSetting.BooleanUserStyleSetting( + UserStyleSetting.Id(DETAILED_AMBIENT_SETTING), + context.resources, + R.string.detailed_ambient_setting, + R.string.detailed_ambient_setting_description, + null, + listOf(WatchFaceLayer.BASE), + false, + ) + // 4. Create style settings to hold all options. return UserStyleSchema( listOf( @@ -352,6 +363,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { secondsStyleSetting, militaryTimeSetting, bigAmbientSetting, + detailedAmbientSetting, ) ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 700606d..4b8a1d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -145,12 +145,14 @@ Hour Pips Military Time Big Ambient + Detailed Ambient Whether to draw or not Whether to use military instead of standard time Use big time on ambient mode + Show complications on ambient mode From 5d9c62fee7c9ef6de24612d9dcbf900ce1fe8efd Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 11 Feb 2024 17:53:02 +0200 Subject: [PATCH 12/52] Fix text scaling for big/detailed ambient --- app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index b44f804..61972c9 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -558,10 +558,8 @@ class WatchCanvasRenderer( if (watchFaceData.detailedAmbient) { 14f } else { - interpolate(18f, 14f) + interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) } - -// 14f + (1f - scale) * (18f - 14f); } } From 39cb5ddbc81ceb9cc300d9e1b504fdc6871f71ac Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 11 Feb 2024 17:55:30 +0200 Subject: [PATCH 13/52] Disable debug complication bounds bg --- .../java/dev/rdnt/m8face/utils/HorizontalComplication.kt | 7 ++++--- .../main/java/dev/rdnt/m8face/utils/IconComplication.kt | 7 ++++--- .../java/dev/rdnt/m8face/utils/InvisibleComplication.kt | 7 ++++--- .../java/dev/rdnt/m8face/utils/VerticalComplication.kt | 7 ++++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index 02e8ed5..e2a68a9 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -84,9 +84,10 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) { if (bounds.isEmpty) return - canvas.drawRect(bounds, Paint().apply { - color = Color.parseColor("#22ffffff") - }) +// // DEBUG +// canvas.drawRect(bounds, Paint().apply { +// color = Color.parseColor("#22ffffff") +// }) when (data.type) { ComplicationType.SHORT_TEXT -> { diff --git a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt index bdcb6db..6c317df 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt @@ -48,9 +48,10 @@ class IconComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return - canvas.drawRect(bounds, Paint().apply { - color = Color.parseColor("#22ffffff") - }) +// // DEBUG +// canvas.drawRect(bounds, Paint().apply { +// color = Color.parseColor("#22ffffff") +// }) when (data.type) { ComplicationType.MONOCHROMATIC_IMAGE -> { diff --git a/app/src/main/java/dev/rdnt/m8face/utils/InvisibleComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/InvisibleComplication.kt index 1357fcf..5a54a34 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/InvisibleComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/InvisibleComplication.kt @@ -25,9 +25,10 @@ class InvisibleComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return - canvas.drawRect(bounds, Paint().apply { - color = Color.parseColor("#22ffffff") - }) +// // DEBUG +// canvas.drawRect(bounds, Paint().apply { +// color = Color.parseColor("#22ffffff") +// }) } override fun drawHighlight( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index f52ad5b..a213e83 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -78,9 +78,10 @@ class VerticalComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return - canvas.drawRect(bounds, Paint().apply { - color = Color.parseColor("#22ffffff") - }) +// // DEBUG +// canvas.drawRect(bounds, Paint().apply { +// color = Color.parseColor("#22ffffff") +// }) when (data.type) { ComplicationType.SHORT_TEXT -> { From 68283d8a1cacd827e7c9004a6d6edd645ebc685e Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Tue, 13 Feb 2024 01:54:49 +0200 Subject: [PATCH 14/52] Better seconds rendering, wip bitmap cache --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 368 +++++++++++++----- .../rdnt/m8face/utils/ComplicationUtils.kt | 31 +- .../m8face/utils/HorizontalComplication.kt | 56 ++- .../utils/HorizontalTextComplication.kt | 173 ++++++++ .../rdnt/m8face/utils/VerticalComplication.kt | 115 +++++- 5 files changed, 607 insertions(+), 136 deletions(-) create mode 100644 app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 61972c9..2b2a215 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -29,7 +29,6 @@ import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF import android.util.FloatProperty -import android.util.Log import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep @@ -55,6 +54,7 @@ import dev.rdnt.m8face.utils.BIG_AMBIENT_SETTING import dev.rdnt.m8face.utils.COLOR_STYLE_SETTING import dev.rdnt.m8face.utils.DETAILED_AMBIENT_SETTING import dev.rdnt.m8face.utils.HorizontalComplication +import dev.rdnt.m8face.utils.HorizontalTextComplication import dev.rdnt.m8face.utils.IconComplication import dev.rdnt.m8face.utils.LAYOUT_STYLE_SETTING import dev.rdnt.m8face.utils.MILITARY_TIME_SETTING @@ -189,6 +189,13 @@ class WatchCanvasRenderer( color = watchFaceColors.secondaryColor } + private val secondPaint = Paint().apply { + isAntiAlias = true // make sure text is not anti-aliased even with this on + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 112f / 14f + color = watchFaceColors.tertiaryColor + } + private val ambientMinutePaint = Paint().apply { isAntiAlias = true val big = watchFaceData.bigAmbient @@ -330,7 +337,8 @@ class WatchCanvasRenderer( private fun updateRefreshRate() { interactiveDrawModeUpdateDelayMillis = when { animating() -> 16 // 60 fps cause animating - watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 60000 // update once a second +// watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 60000 // update once a second + watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 1000 // update once a second // TODO: handle digital seconds watchFaceData.secondsStyle.id == SecondsStyle.DASHES.id -> 16 // 60 fps watchFaceData.secondsStyle.id == SecondsStyle.DOTS.id -> 16 // 60 fps else -> 60000 // safe default @@ -441,6 +449,8 @@ class WatchCanvasRenderer( minutePaint.color = watchFaceColors.secondaryColor ambientMinutePaint.color = watchFaceColors.secondaryColor + secondPaint.color = watchFaceColors.tertiaryColor + batteryPaint.color = watchFaceColors.tertiaryColor batteryIconPaint.color = watchFaceColors.tertiaryColor @@ -505,6 +515,9 @@ class WatchCanvasRenderer( is HorizontalComplication -> (complication.renderer as HorizontalComplication).tertiaryColor = watchFaceColors.tertiaryColor + is HorizontalTextComplication -> (complication.renderer as HorizontalTextComplication).tertiaryColor = + watchFaceColors.tertiaryColor + is IconComplication -> (complication.renderer as IconComplication).tertiaryColor = watchFaceColors.tertiaryColor @@ -545,7 +558,7 @@ class WatchCanvasRenderer( get() = if (!isHeadless) drawProperties.timeScale else 1f fun interpolate(start: Float, end: Float): Float { - return start + scale * (end - start) + return start + easeInOutCubic(scale) * (end - start) } val timeTextSize: Float @@ -563,26 +576,33 @@ class WatchCanvasRenderer( } } - val hourOffsetX: Float + val timeOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { -// 81f; if (watchFaceData.detailedAmbient) { -34f } else { interpolate(0f, -34f) } + } + else -> { + 0f } + } -// LayoutStyle.FOCUS.id -> { -//// 93f; -// 0f -// } + val minutesOffsetX: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + if (watchFaceData.detailedAmbient) { + -34f - 11f + } else { + interpolate(0f, -34f- 11f) + } + } else -> { 0f -// interpolate(93f, 115f) } } @@ -598,31 +618,74 @@ class WatchCanvasRenderer( } } - val minuteOffsetX: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> { - 81f - } +// val minuteOffsetX: Float +// get() = when (watchFaceData.layoutStyle.id) { +// LayoutStyle.SPORT.id -> { +// 81f +// } +// +// LayoutStyle.FOCUS.id -> { +// 93f; +// } +// +// else -> { +//// 115f; +// interpolate(93f, 115f) +// } +// } + val minuteOffsetY: Float + get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { - 93f; + 327f } else -> { -// 115f; - interpolate(93f, 115f) + 297f; +// interpolate(327f, 297f) } } - val minuteOffsetY: Float + val shouldDrawSeconds: Boolean + get() = when(watchFaceData.layoutStyle.id) { + LayoutStyle.INFO1.id -> true + LayoutStyle.INFO3.id -> true + LayoutStyle.SPORT.id -> true + else -> false + } + + val secondsOffsetY: Float + get() = when(watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> -28f + else -> 0f + } + + val shouldDrawAmPm: Boolean + get() = when(watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> true + else -> false + } + + val secondsTextSize: Float + get() = when(watchFaceData.layoutStyle.id) { + LayoutStyle.INFO1.id -> 6f + LayoutStyle.INFO3.id -> 6f + LayoutStyle.SPORT.id -> 7f + else -> 0f + } + + val secondsTextScale: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { - 327f + 18f / 14f } else -> { - 297f; -// interpolate(327f, 297f) + if (watchFaceData.detailedAmbient) { + 14f / 14f + } else { + interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) + } } } @@ -638,6 +701,14 @@ class WatchCanvasRenderer( } } + fun getAmPm(zonedDateTime: ZonedDateTime): String { + return if (zonedDateTime.hour < 12) { + "am" + } else { + "pm" + } + } + override fun render( canvas: Canvas, bounds: Rect, @@ -654,22 +725,48 @@ class WatchCanvasRenderer( bounds, getHour(zonedDateTime), hourPaint, - hourOffsetX, - hourOffsetY, + timeOffsetX, + -7f-49f, timeTextSize, -// scale, ) -// drawTime( -// canvas, -// bounds, -// zonedDateTime.minute, -// hourPaint, -// minuteOffsetX, -// minuteOffsetY, -// timeTextSize, -//// scale, -// ) + drawTime( + canvas, + bounds, + zonedDateTime.minute, + minutePaint, + timeOffsetX, + 7f+49f, + timeTextSize, + ) + + if (shouldDrawSeconds) { + drawSeconds( + canvas, + bounds, + zonedDateTime.second, + secondPaint, + timeOffsetX, + secondsOffsetY, + secondsTextSize, + secondsTextScale, + ) + } + + + if (shouldDrawAmPm) { + drawAmPm( + canvas, + bounds, + zonedDateTime + "pm", + secondPaint, + timeOffsetX, + 25f, + timeTextSize, + ) + } + // if (drawProperties.timeScale == 0f) { // var hourOffsetX = 0f @@ -766,11 +863,11 @@ class WatchCanvasRenderer( } - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - watchFaceData.detailedAmbient || drawProperties.timeScale != 0f - ) { +// if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && +// watchFaceData.detailedAmbient || drawProperties.timeScale != 0f +// ) { drawComplications(canvas, zonedDateTime) - } +// } // if (renderParameters.watchFaceLayesr.contains(WatchFaceLayer.COMPLICATIONS) && // drawProperties.timeScale != 0f @@ -789,16 +886,38 @@ class WatchCanvasRenderer( // ----- All drawing functions ----- private fun drawComplications(canvas: Canvas, zonedDateTime: ZonedDateTime) { - val opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) + var opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) + opacity = 1f + val offsetX = if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id) interpolate(34f, 0f) else 0f + val scale = interpolate(16f / 14f, 1f) for ((_, complication) in complicationSlotsManager.complicationSlots) { if (complication.enabled) { when (complication.renderer) { - is VerticalComplication -> (complication.renderer as VerticalComplication).opacity = - opacity + is VerticalComplication -> { + (complication.renderer as VerticalComplication).opacity = + opacity + (complication.renderer as VerticalComplication).scale = + scale + } - is HorizontalComplication -> (complication.renderer as HorizontalComplication).opacity = - opacity + is HorizontalComplication -> { + (complication.renderer as HorizontalComplication).opacity = + opacity + (complication.renderer as HorizontalComplication).offsetX = + offsetX + (complication.renderer as HorizontalComplication).scale = + scale + } + + is HorizontalTextComplication -> { + (complication.renderer as HorizontalTextComplication).opacity = + opacity + (complication.renderer as HorizontalTextComplication).offsetX = + offsetX + (complication.renderer as HorizontalTextComplication).scale = + scale + } is IconComplication -> (complication.renderer as IconComplication).opacity = opacity @@ -811,43 +930,77 @@ class WatchCanvasRenderer( } } - private fun drawTime( + private fun drawSeconds( canvas: Canvas, bounds: Rect, time: Int, paint: Paint, offsetX: Float, offsetY: Float, - textSize: Float + textSize: Float, + textScale: Float, ) { - -// val offsetX = -34f val text = time.toString().padStart(2, '0') + var opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) + opacity = 1f + val p = Paint(paint) - p.textSize *= 14f - p.isAntiAlias = true - p.isDither = true - p.isFilterBitmap = true + p.textSize *= textSize -// val textBounds = Rect() -// p.getTextBounds(text, 0, text.length, textBounds) + val cacheBitmap = Bitmap.createBitmap( + bounds.width(), + bounds.height(), + Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(cacheBitmap) - var scale = 1f - scale *= textSize / 14f + val textBounds = Rect() + p.getTextBounds(text, 0, text.length, textBounds) + p.color = ColorUtils.blendARGB(Color.TRANSPARENT, watchFaceColors.tertiaryColor, opacity) -// canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { -// canvas.drawText( -// text, -//// 192f-textBounds.width().toFloat()/2f, 192f - textBounds.height().toFloat()/2f, -// 192f-textBounds.width().toFloat()/2f, 192f-7f, -//// offsetX, -//// offsetY, -// p -// ) -// } + bitmapCanvas.drawText( + text, + 192f, + 192f+textBounds.height()/2+offsetY, + p, + ) - ///////////////////////// + + canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withTranslation(offsetX, 0f) { + canvas.drawBitmap( + cacheBitmap, + 91f, + 0f, + Paint(), + ) + } + } + } + + private fun drawAmPm( + canvas: Canvas, + bounds: Rect, + ampm: String, + paint: Paint, + offsetX: Float, + offsetY: Float, + _scale: Float, + ) { + val text = ampm.uppercase() + + var opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) + opacity = 1f + + val p = Paint(paint) + p.textSize *= 5f + p.isAntiAlias = true + p.isDither = true + p.isFilterBitmap = true + + var scale = _scale / 14f +// scale = 1f val cacheBitmap = Bitmap.createBitmap( bounds.width(), @@ -855,67 +1008,77 @@ class WatchCanvasRenderer( Bitmap.Config.ARGB_8888 ) val bitmapCanvas = Canvas(cacheBitmap) -// val bitmapPaint = Paint() -// bitmapPaint.textSize = 14f - val textBounds = Rect() p.getTextBounds(text, 0, text.length, textBounds) + p.color = ColorUtils.blendARGB(Color.TRANSPARENT, watchFaceColors.tertiaryColor, opacity) bitmapCanvas.drawText( text, - 192f-textBounds.width().toFloat()/2f, - 192f+7f+textBounds.height(), + 192f, + 192f+textBounds.height()/2+offsetY, p, ) - Log.d("@@@", "$offsetX") - - canvas.withTranslation(offsetX, 0f) { - canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withTranslation(offsetX, 0f) { canvas.drawBitmap( cacheBitmap, - 0f, + 91f, 0f, Paint(), ) } } + } + + private fun drawTime( + canvas: Canvas, + bounds: Rect, + time: Int, + paint: Paint, + offsetX: Float, + offsetY: Float, + textSize: Float + ) { + val text = time.toString().padStart(2, '0') + val p = Paint(paint) + p.textSize *= 14f + p.isAntiAlias = true + p.isDither = true + p.isFilterBitmap = true - /////////////////////// + var scale = 1f + scale *= textSize / 14f - val cacheBitmap2 = Bitmap.createBitmap( + val cacheBitmap = Bitmap.createBitmap( bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 ) - val bitmapCanvas2 = Canvas(cacheBitmap2) -// val bitmapPaint = Paint() -// bitmapPaint.textSize = 14f - + val bitmapCanvas = Canvas(cacheBitmap) - val textBounds2 = Rect() - p.getTextBounds(text, 0, text.length, textBounds2) + val textBounds = Rect() + p.getTextBounds(text, 0, text.length, textBounds) - bitmapCanvas2.drawText( + bitmapCanvas.drawText( text, - 192f-textBounds2.width().toFloat()/2f, - 192f-7f, + 192f-textBounds.width().toFloat()/2f, + 192f+textBounds.height()/2+offsetY, p, ) - canvas.withTranslation(offsetX, 0f) { - canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withTranslation(offsetX, 0f) { canvas.drawBitmap( - cacheBitmap2, + cacheBitmap, 0f, 0f, Paint(), ) } } - } private fun drawTimeBitmap( @@ -1387,6 +1550,14 @@ class WatchCanvasRenderer( } } + private fun easeInOutCubic(x: Float): Float { + return if (x < 0.5f) { + 4 * x * x * x + } else { + 1 - (-2f * x + 2f).pow(3f) / 2 + } + } + private class DrawProperties( var timeScale: Float = 0f ) { @@ -1425,3 +1596,16 @@ fun bitmapCache(canvas: Canvas, bounds: Rect) { canvas.drawBitmap(cacheBitmap, 0f, 0f, Paint()) } } + +class BitmapCache { + private val key: String = "" + private val bitmap: Bitmap? = null; + + fun get(k: String): Bitmap { + if (bitmap != null && k == key) { + return bitmap + } + + + } +} diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index c1c5577..8343280 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -104,23 +104,23 @@ internal const val SPORT_MINUTE_COMPLICATION_ID = 120 const val TOP_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f const val TOP_LEFT_COMPLICATION_TOP_BOUND = 93f / 384f -const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 66f / 384f -const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 93f / 384f + 87f / 384f +const val TOP_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 60f / 384f +const val TOP_LEFT_COMPLICATION_BOTTOM_BOUND = 93f / 384f + 90f / 384f const val BOTTOM_LEFT_COMPLICATION_LEFT_BOUND = 33f / 384f -const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 204f / 384f -const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 66f / 384f -const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 204f / 384f + 87f / 384f +const val BOTTOM_LEFT_COMPLICATION_TOP_BOUND = 201f / 384f +const val BOTTOM_LEFT_COMPLICATION_RIGHT_BOUND = 33f / 384f + 60f / 384f +const val BOTTOM_LEFT_COMPLICATION_BOTTOM_BOUND = 201f / 384f + 90f / 384f const val TOP_RIGHT_COMPLICATION_LEFT_BOUND = 285f / 384f const val TOP_RIGHT_COMPLICATION_TOP_BOUND = 93f / 384f -const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 285f / 384f + 66f / 384f -const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 93f / 384f + 87f / 384f +const val TOP_RIGHT_COMPLICATION_RIGHT_BOUND = 285f / 384f + 60f / 384f +const val TOP_RIGHT_COMPLICATION_BOTTOM_BOUND = 93f / 384f + 90f / 384f const val BOTTOM_RIGHT_COMPLICATION_LEFT_BOUND = 285f / 384f -const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 204f / 384f -const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 285f / 384f + 66f / 384f -const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 204f / 384f + 87f / 384f +const val BOTTOM_RIGHT_COMPLICATION_TOP_BOUND = 201f / 384f +const val BOTTOM_RIGHT_COMPLICATION_RIGHT_BOUND = 285f / 384f + 60f / 384f +const val BOTTOM_RIGHT_COMPLICATION_BOTTOM_BOUND = 201f / 384f + 90f / 384f const val LEFT_ICON_COMPLICATION_LEFT_BOUND = 24f / 384f const val LEFT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f @@ -132,10 +132,10 @@ const val RIGHT_ICON_COMPLICATION_TOP_BOUND = 126f / 384f const val RIGHT_ICON_COMPLICATION_RIGHT_BOUND = 306f / 384f + 54f / 384f const val RIGHT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f -const val RIGHT_TEXT_COMPLICATION_LEFT_BOUND = 248f / 384f -const val RIGHT_TEXT_COMPLICATION_TOP_BOUND = 248f / 384f -const val RIGHT_TEXT_COMPLICATION_RIGHT_BOUND = 248f / 384f + 66f / 384f -const val RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND = 248f / 384f + 15f / 384f +const val RIGHT_TEXT_COMPLICATION_LEFT_BOUND = 249f / 384f - 14f / 384f +const val RIGHT_TEXT_COMPLICATION_TOP_BOUND = 246f / 384f - 14f / 384f +const val RIGHT_TEXT_COMPLICATION_RIGHT_BOUND = 249f / 384f + 82f / 384f + 14f / 384f +const val RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND = 246f / 384f + 14f / 384f + 14f / 384f const val HOUR_COMPLICATION_LEFT_BOUND = 114f / 384f const val HOUR_COMPLICATION_TOP_BOUND = 87f / 384f @@ -492,6 +492,7 @@ fun createComplicationSlotManager( val verticalComplicationFactory = createVerticalComplicationFactory(context) val horizontalComplicationFactory = createHorizontalComplicationFactory(context) val invisibleComplicationFactory = createInvisibleComplicationFactory(context) + val horizontalTextComplicationFactory = createHorizontalTextComplicationFactory(context) val hourComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = HOUR_COMPLICATION_ID, @@ -777,7 +778,7 @@ fun createComplicationSlotManager( val textComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = RIGHT_TEXT_COMPLICATION_ID, - canvasComplicationFactory = horizontalComplicationFactory, + canvasComplicationFactory = horizontalTextComplicationFactory, supportedTypes = listOf( ComplicationType.SHORT_TEXT, ), diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index e2a68a9..2d9087b 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -12,6 +12,8 @@ import android.graphics.RectF import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.withScale +import androidx.core.graphics.withTranslation import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters @@ -49,6 +51,10 @@ class HorizontalComplication(private val context: Context) : CanvasComplication prefixPaint.alpha = 100 } + var offsetX: Float = 0f + + var scale: Float = 1f + private val textPaint = Paint().apply { isAntiAlias = true typeface = context.resources.getFont(R.font.m8stealth57) @@ -122,11 +128,11 @@ class HorizontalComplication(private val context: Context) : CanvasComplication if (isBattery) { val drawable = ContextCompat.getDrawable(context, R.drawable.battery_icon_32)!! icon = drawable.toBitmap( - (32f / 48f * bounds.height()).toInt(), - (32f / 48f * bounds.height()).toInt() + (32f / 384 * canvas.width).toInt(), + (32f / 384 * canvas.width).toInt() ) iconBounds = - Rect(0, 0, (32f / 48f * bounds.height()).toInt(), (32f / 48f * bounds.height()).toInt()) + Rect(0, 0, (32f / 384 * canvas.width).toInt(), (32f / 384 * canvas.width).toInt()) } else if (data.monochromaticImage != null) { val drawable = data.monochromaticImage!!.image.loadDrawable(context) if (drawable != null) { @@ -148,7 +154,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication title = data.title!!.getTextAt(context.resources, now).toString().uppercase() } - textPaint.textSize = 24F / 48f * bounds.height() + textPaint.textSize = 24F / 384 * canvas.width val textBounds = Rect() @@ -175,24 +181,37 @@ class HorizontalComplication(private val context: Context) : CanvasComplication titleOffsetX = (width - titleBounds.width()).toFloat() / 2f textOffsetX = (width - textBounds.width()).toFloat() / 2f - titleOffsetX += 6f / 156f * bounds.width() - textOffsetX += 6f / 156f * bounds.width() + titleOffsetX += 6f / 384f * canvas.width + textOffsetX += 6f / 384f * canvas.width } else if (icon != null) { val width = iconBounds.width() + textBounds.width() iconOffsetX = (width - iconBounds.width()).toFloat() / 2f textOffsetX = (width - textBounds.width()).toFloat() / 2f - iconOffsetX += 9f / 156f * bounds.width() - textOffsetX += 9f / 156f * bounds.width() + iconOffsetX += 9f / 384f * canvas.width + textOffsetX += 9f / 384f * canvas.width if (isBattery) { iconOffsetX = iconOffsetX.toInt().toFloat() } } +// textOffsetX += offsetX +// titleOffsetX += offsetX +// iconOffsetX -= offsetX + + + val cacheBitmap = Bitmap.createBitmap( + canvas.width, + canvas.height, + Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(cacheBitmap) + + if (title != null) { - canvas.drawText( + bitmapCanvas.drawText( title, bounds.exactCenterX() - titleBounds.width() / 2 - titleOffsetX, bounds.exactCenterY() + titleBounds.height() / 2, @@ -206,14 +225,15 @@ class HorizontalComplication(private val context: Context) : CanvasComplication bounds.exactCenterY() + iconBounds.height() / 2f, ) - canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) + bitmapCanvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) } + if (prefixLen > 0) { val prefix = "".padStart(prefixLen, '0') prefixPaint.textSize = textPaint.textSize - canvas.drawText( + bitmapCanvas.drawText( prefix, bounds.exactCenterX() - textBounds.width() / 2 + textOffsetX, bounds.exactCenterY() + textBounds.height() / 2, @@ -221,12 +241,24 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) } - canvas.drawText( + bitmapCanvas.drawText( text, bounds.exactCenterX() - textBounds.width() / 2 + textOffsetX, bounds.exactCenterY() + textBounds.height() / 2, textPaint ) + + + canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + canvas.withTranslation(offsetX, 0f) { + canvas.drawBitmap( + cacheBitmap, + 0f, + 0f, + Paint(), + ) + } + } } override fun drawHighlight( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt new file mode 100644 index 0000000..bbd8646 --- /dev/null +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt @@ -0,0 +1,173 @@ +package dev.rdnt.m8face.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.Rect +import android.graphics.RectF +import android.util.Log +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.withScale +import androidx.core.graphics.withTranslation +import androidx.wear.watchface.CanvasComplication +import androidx.wear.watchface.CanvasComplicationFactory +import androidx.wear.watchface.RenderParameters +import androidx.wear.watchface.complications.data.ComplicationData +import androidx.wear.watchface.complications.data.ComplicationType +import androidx.wear.watchface.complications.data.NoDataComplicationData +import androidx.wear.watchface.complications.data.ShortTextComplicationData +import dev.rdnt.m8face.R +import java.time.Instant +import java.time.ZonedDateTime + +class HorizontalTextComplication(private val context: Context) : CanvasComplication { + var tertiaryColor: Int = Color.parseColor("#8888bb") + set(tertiaryColor) { + field = tertiaryColor + textPaint.color = tertiaryColor + } + + var opacity: Float = 1f + set(opacity) { + field = opacity + + val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, opacity) + + textPaint.color = color + } + + var offsetX: Float = 0f + + var scale: Float = 0f + + private val textPaint = Paint().apply { + isAntiAlias = true + typeface = context.resources.getFont(R.font.m8stealth57) + textAlign = Paint.Align.LEFT + color = tertiaryColor + textSize = 112f / 14f / 7f + } + + override fun render( + canvas: Canvas, + bounds: Rect, + zonedDateTime: ZonedDateTime, + renderParameters: RenderParameters, + slotId: Int + ) { + if (bounds.isEmpty) return + +// // DEBUG +// canvas.drawRect(bounds, Paint().apply { +// color = Color.parseColor("#22ffffff") +// }) + + when (data.type) { + ComplicationType.SHORT_TEXT -> { + renderShortTextComplication(canvas, bounds, data as ShortTextComplicationData) + } + + else -> return + } + } + + private fun renderShortTextComplication( + canvas: Canvas, + bounds: Rect, + data: ShortTextComplicationData, + ) { + val now = Instant.now() + + var text = data.text.getTextAt(context.resources, now).toString().uppercase() + if (text == "--") { + return + } + + val p = Paint(textPaint) + p.textSize *= 14f / 42f * bounds.height() + +// val textBounds = Rect() +// p.getTextBounds(text, 0, text.length, textBounds) + + + + + val cacheBitmap = Bitmap.createBitmap( + bounds.width(), + bounds.height(), + Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(cacheBitmap) + + val textBounds = Rect() + p.getTextBounds(text, 0, text.length, textBounds) + + bitmapCanvas.drawText( + text, +// 192f-textBounds.width().toFloat()/2f, +// 192f+textBounds.height()/2+offsetY, + 0f, + bounds.height().toFloat()/2f, +// bounds.left.toFloat() + 14f / 42f * bounds.height(), +// bounds.exactCenterY() + textBounds.height() / 2, + p, + ) + + + + + + canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + canvas.withTranslation(offsetX, 0f) { + canvas.drawBitmap( + cacheBitmap, + bounds.left.toFloat() + 14f / 42f * bounds.height(), +// bounds.top.toFloat(), + bounds.top.toFloat() + textBounds.height() / 2, +// bounds.left.toFloat() + 14f / 42f * bounds.height(), +// bounds.exactCenterY() + textBounds.height() / 2, + + Paint(), + ) + +// canvas.drawText( +// text, +// bounds.left.toFloat() + 14f / 42f * bounds.height(), +// bounds.exactCenterY() + textBounds.height() / 2, +// p +// ) + } + } + } + + override fun drawHighlight( + canvas: Canvas, + bounds: Rect, + boundsType: Int, + zonedDateTime: ZonedDateTime, + color: Int + ) { + // Rendering of highlights + } + + private var data: ComplicationData = NoDataComplicationData() + + override fun getData(): ComplicationData = data + + override fun loadData( + complicationData: ComplicationData, + loadDrawablesAsynchronous: Boolean + ) { + data = complicationData + } +} + +fun createHorizontalTextComplicationFactory(context: Context) = CanvasComplicationFactory { _, _ -> + HorizontalTextComplication(context) +} diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index a213e83..cad06e3 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -7,6 +7,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.toRect +import androidx.core.graphics.withScale import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters @@ -41,8 +42,12 @@ class VerticalComplication(private val context: Context) : CanvasComplication { prefixPaint.alpha = 100 } + var scale: Float = 1f + private val textPaint = Paint().apply { isAntiAlias = true + isDither = true + isFilterBitmap = true typeface = context.resources.getFont(R.font.m8stealth57) textAlign = Paint.Align.LEFT color = tertiaryColor @@ -128,11 +133,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { if (isBattery) { val drawable = ContextCompat.getDrawable(context, R.drawable.battery_icon_32)!! icon = drawable.toBitmap( - (32f / 72f * bounds.width()).toInt(), - (32f / 72f * bounds.width()).toInt() + (32f / 384f * canvas.width).toInt(), + (32f / 384f * canvas.width).toInt() ) iconBounds = - Rect(0, 0, (32f / 72f * bounds.width()).toInt(), (32f / 72f * bounds.width()).toInt()) + Rect(0, 0, (32f / 384f * canvas.width).toInt(), (32f / 384f * canvas.width).toInt()) } else if (data.monochromaticImage != null) { val drawable = data.monochromaticImage!!.image.loadDrawable(context) if (drawable != null) { @@ -155,11 +160,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { } if (text.length <= 3) { - textPaint.textSize = 24F / 72f * bounds.width() + textPaint.textSize = 24F / 384f * canvas.width } else if (text.length <= 6) { - textPaint.textSize = 16F / 72f * bounds.width() + textPaint.textSize = 16F / 384f * canvas.width } else { - textPaint.textSize = 12F / 72f * bounds.width() + textPaint.textSize = 12F / 384f * canvas.width } val textBounds = Rect() @@ -174,11 +179,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { if (title != null) { if (title.length <= 3) { - titlePaint.textSize = 24F / 72f * bounds.width() + titlePaint.textSize = 24F / 384f * canvas.width } else if (title.length <= 6) { - titlePaint.textSize = 16F / 72f * bounds.width() + titlePaint.textSize = 16F / 384f * canvas.width } else { - titlePaint.textSize = 12F / 72f * bounds.width() + titlePaint.textSize = 12F / 384f * canvas.width } titlePaint.getTextBounds(title, 0, title.length, titleBounds) @@ -194,22 +199,37 @@ class VerticalComplication(private val context: Context) : CanvasComplication { iconOffsetY = (height - iconBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - iconOffsetY += 9f / 132f * bounds.height() + iconOffsetY += 6f / 384f * canvas.width if (isBattery) { iconOffsetY = iconOffsetY.toInt().toFloat() } - textOffsetY += 3f / 132f * bounds.height() + textOffsetY += 6f / 384f * canvas.width } else if (title != null) { val height = titleBounds.height() + textBounds.height() titleOffsetY = (height - titleBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - titleOffsetY += 6f / 132f * bounds.height() - textOffsetY += 6f / 132f * bounds.height() + titleOffsetY += 6f / 384f * canvas.width + textOffsetY += 6f / 384f * canvas.width } + + + + + + + val cacheBitmap = Bitmap.createBitmap( + canvas.width, + canvas.height, + Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(cacheBitmap) + + + if (icon != null) { val dstRect = RectF( bounds.exactCenterX() - iconBounds.width() / 2, @@ -218,9 +238,9 @@ class VerticalComplication(private val context: Context) : CanvasComplication { bounds.exactCenterY() + iconBounds.height() / 2 - iconOffsetY, ) - canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) + bitmapCanvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) } else if (title != null) { - canvas.drawText( + bitmapCanvas.drawText( title, bounds.exactCenterX() - titleBounds.width() / 2, bounds.exactCenterY() + titleBounds.height() / 2 - titleOffsetY, @@ -232,7 +252,7 @@ class VerticalComplication(private val context: Context) : CanvasComplication { val prefix = "".padStart(prefixLen, '0') prefixPaint.textSize = textPaint.textSize - canvas.drawText( + bitmapCanvas.drawText( prefix, bounds.exactCenterX() - textBounds.width() / 2, bounds.exactCenterY() + textBounds.height() / 2 + textOffsetY, @@ -240,12 +260,73 @@ class VerticalComplication(private val context: Context) : CanvasComplication { ) } - canvas.drawText( + bitmapCanvas.drawText( text, bounds.exactCenterX() - textBounds.width() / 2, bounds.exactCenterY() + textBounds.height() / 2 + textOffsetY, textPaint ) + + + + canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + canvas.drawBitmap( + cacheBitmap, + 0f, + 0f, + Paint(), + ) + } + + + + + + + + return + + canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + + if (icon != null) { + val dstRect = RectF( + bounds.exactCenterX() - iconBounds.width() / 2, + bounds.exactCenterY() - iconBounds.height() / 2 - iconOffsetY, + bounds.exactCenterX() + iconBounds.width() / 2, + bounds.exactCenterY() + iconBounds.height() / 2 - iconOffsetY, + ) + + canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) + } else if (title != null) { + canvas.drawText( + title, + bounds.exactCenterX() - titleBounds.width() / 2, + bounds.exactCenterY() + titleBounds.height() / 2 - titleOffsetY, + titlePaint + ) + } + + if (prefixLen > 0) { + val prefix = "".padStart(prefixLen, '0') + prefixPaint.textSize = textPaint.textSize + + canvas.drawText( + prefix, + bounds.exactCenterX() - textBounds.width() / 2, + bounds.exactCenterY() + textBounds.height() / 2 + textOffsetY, + prefixPaint + ) + } + + canvas.drawText( + text, + bounds.exactCenterX() - textBounds.width() / 2, + bounds.exactCenterY() + textBounds.height() / 2 + textOffsetY, + textPaint + ) + + } + } private fun renderMonochromaticImageComplication( From d4887a4dada9aeef3f971e413209a442c4b505ac Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Tue, 13 Feb 2024 22:45:24 +0200 Subject: [PATCH 15/52] Bitmap cache for seconds --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 141 ++++++++++++++---- 1 file changed, 111 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 2b2a215..413ccbc 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -251,6 +251,8 @@ class WatchCanvasRenderer( private var isHeadless = false private var isAmbient = false + private val secondsBitmapCache: BitmapCache = BitmapCache() + private val ambientExitAnimator = AnimatorSet().apply { val linearOutSlow = @@ -654,9 +656,30 @@ class WatchCanvasRenderer( else -> false } +// val secondsOffsetX: Float +// get() = when(watchFaceData.layoutStyle.id) { +// LayoutStyle.SPORT.id -> 95f +// else -> 0f +// } + + val secondsOffsetX: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + if (watchFaceData.detailedAmbient) { + 95f + } else { + interpolate(129f, 95f) + } + } + + else -> { + 0f + } + } + val secondsOffsetY: Float get() = when(watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> -28f + LayoutStyle.SPORT.id -> -31f else -> 0f } @@ -689,6 +712,21 @@ class WatchCanvasRenderer( } } + val ampmTextScale: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 18f / 14f + } + + else -> { + if (watchFaceData.detailedAmbient) { + 14f / 14f + } else { + interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) + } + } + } + fun getHour(zonedDateTime: ZonedDateTime): Int { if (is24Format) { return zonedDateTime.hour @@ -746,7 +784,9 @@ class WatchCanvasRenderer( bounds, zonedDateTime.second, secondPaint, - timeOffsetX, +// 95f, +// -31f, + secondsOffsetX, secondsOffsetY, secondsTextSize, secondsTextScale, @@ -758,12 +798,11 @@ class WatchCanvasRenderer( drawAmPm( canvas, bounds, - zonedDateTime - "pm", + getAmPm(zonedDateTime), secondPaint, timeOffsetX, 25f, - timeTextSize, + ampmTextScale, ) } @@ -945,38 +984,71 @@ class WatchCanvasRenderer( var opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) opacity = 1f + val bitmap = renderSeconds(text, paint, textSize, watchFaceColors.tertiaryColor, opacity) + + canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.drawBitmap( + bitmap, + 192f - bitmap.width/2 + offsetX, + 192f - bitmap.height/2 + offsetY, + Paint(), + ) + } + } + + private fun renderSeconds( + text: String, + paint: Paint, + textSize: Float, + color: Int, + opacity: Float, + ): Bitmap { + val key = "${text},${textSize},${color},${opacity}" + + val cached = secondsBitmapCache.get(key) + if (cached != null) { + return cached + } + val p = Paint(paint) p.textSize *= textSize + p.color = ColorUtils.blendARGB(Color.TRANSPARENT, color, opacity) - val cacheBitmap = Bitmap.createBitmap( + val textBounds = Rect() + p.getTextBounds(text, 0, text.length, textBounds) + val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) + + val bitmap = Bitmap.createBitmap( bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 ) - val bitmapCanvas = Canvas(cacheBitmap) - - val textBounds = Rect() - p.getTextBounds(text, 0, text.length, textBounds) - p.color = ColorUtils.blendARGB(Color.TRANSPARENT, watchFaceColors.tertiaryColor, opacity) + val canvas = Canvas(bitmap) - bitmapCanvas.drawText( + canvas.drawText( text, - 192f, - 192f+textBounds.height()/2+offsetY, + 0f, + bounds.height().toFloat(), p, ) + // DEBUG -------------------- + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#22ffffff") + }) + val p2 = Paint() + p2.color = Color.WHITE + canvas.drawText( + "r ${secondsBitmapCache.loads} w ${secondsBitmapCache.renders}", + 0f, + bounds.height().toFloat(), + p2, + ) + // --------------------------- - canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.withTranslation(offsetX, 0f) { - canvas.drawBitmap( - cacheBitmap, - 91f, - 0f, - Paint(), - ) - } - } + secondsBitmapCache.set(key, bitmap) + + return bitmap } private fun drawAmPm( @@ -1016,15 +1088,15 @@ class WatchCanvasRenderer( bitmapCanvas.drawText( text, 192f, - 192f+textBounds.height()/2+offsetY, + 192f+textBounds.height()/2, p, ) canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.withTranslation(offsetX, 0f) { + canvas.withTranslation(offsetX, offsetY) { canvas.drawBitmap( cacheBitmap, - 91f, + 0f, 0f, Paint(), ) @@ -1598,14 +1670,23 @@ fun bitmapCache(canvas: Canvas, bounds: Rect) { } class BitmapCache { - private val key: String = "" - private val bitmap: Bitmap? = null; + var renders: Int = 0 + var loads: Int = 0 + private var key: String = "" + private var bitmap: Bitmap? = null; - fun get(k: String): Bitmap { + fun get(k: String): Bitmap? { + loads++ if (bitmap != null && k == key) { return bitmap } + return null + } + fun set(k: String, b: Bitmap?) { + renders++ + key = k + bitmap = b } } From b6004e01909a3cd94f601cc6f97e27c72994c42c Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Tue, 13 Feb 2024 23:02:39 +0200 Subject: [PATCH 16/52] Fix seconds offset on info modes --- app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 413ccbc..93cc2a0 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -673,7 +673,7 @@ class WatchCanvasRenderer( } else -> { - 0f + 129f } } @@ -784,8 +784,6 @@ class WatchCanvasRenderer( bounds, zonedDateTime.second, secondPaint, -// 95f, -// -31f, secondsOffsetX, secondsOffsetY, secondsTextSize, From c9f8047b7457e0aa8f36ae39bcfdbfb54fc411fb Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Tue, 13 Feb 2024 23:03:33 +0200 Subject: [PATCH 17/52] Format --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 273 ++++++++---------- 1 file changed, 115 insertions(+), 158 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 93cc2a0..45b4c6f 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -99,11 +99,9 @@ class WatchCanvasRenderer( } } - private val scope: CoroutineScope = - CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) - private val coroutineScope: CoroutineScope = - CoroutineScope(Dispatchers.Main.immediate) + private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main.immediate) // Represents all data needed to render the watch face. All value defaults are constants. Only // three values are changeable by the user (color scheme, ticks being rendered, and length of @@ -253,54 +251,44 @@ class WatchCanvasRenderer( private val secondsBitmapCache: BitmapCache = BitmapCache() - private val ambientExitAnimator = - AnimatorSet().apply { - val linearOutSlow = - AnimationUtils.loadInterpolator( - context, - android.R.interpolator.linear_out_slow_in - ) - play( - ObjectAnimator.ofFloat( - drawProperties, - DrawProperties.TIME_SCALE, - drawProperties.timeScale, - 1.0f - ).apply { - duration = ambientTransitionMs - interpolator = linearOutSlow - setAutoCancel(false) - }, - ) - } + private val ambientExitAnimator = AnimatorSet().apply { + val linearOutSlow = AnimationUtils.loadInterpolator( + context, android.R.interpolator.linear_out_slow_in + ) + play( + ObjectAnimator.ofFloat( + drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale, 1.0f + ).apply { + duration = ambientTransitionMs + interpolator = linearOutSlow + setAutoCancel(false) + }, + ) + } - private val ambientEnterAnimator = - AnimatorSet().apply { - val linearOutSlow = - AnimationUtils.loadInterpolator( - context, - android.R.interpolator.linear_out_slow_in - ) + private val ambientEnterAnimator = AnimatorSet().apply { + val linearOutSlow = AnimationUtils.loadInterpolator( + context, android.R.interpolator.linear_out_slow_in + ) - val keyframes = arrayOf( - Keyframe.ofFloat(0f, drawProperties.timeScale), - Keyframe.ofFloat(0.9f, 0f), - Keyframe.ofFloat(1f, 0f) - ) + val keyframes = arrayOf( + Keyframe.ofFloat(0f, drawProperties.timeScale), + Keyframe.ofFloat(0.9f, 0f), + Keyframe.ofFloat(1f, 0f) + ) - val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( - DrawProperties.TIME_SCALE, - *keyframes - ) + val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( + DrawProperties.TIME_SCALE, *keyframes + ) - play( - ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { - duration = ambientTransitionMs * 5 / 9 - interpolator = linearOutSlow - setAutoCancel(false) - }, - ) - } + play( + ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { + duration = ambientTransitionMs * 5 / 9 + interpolator = linearOutSlow + setAutoCancel(false) + }, + ) + } init { scope.launch { @@ -360,8 +348,8 @@ class WatchCanvasRenderer( for (options in userStyle) { when (options.key.id.toString()) { LAYOUT_STYLE_SETTING -> { - val listOption = options.value as - UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption + val listOption = + options.value as UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption newWatchFaceData = newWatchFaceData.copy( layoutStyle = LayoutStyle.getLayoutStyleConfig( @@ -371,8 +359,7 @@ class WatchCanvasRenderer( } COLOR_STYLE_SETTING -> { - val listOption = options.value as - UserStyleSetting.ListUserStyleSetting.ListOption + val listOption = options.value as UserStyleSetting.ListUserStyleSetting.ListOption newWatchFaceData = newWatchFaceData.copy( colorStyle = ColorStyle.getColorStyleConfig( @@ -382,8 +369,7 @@ class WatchCanvasRenderer( } AMBIENT_STYLE_SETTING -> { - val listOption = options.value as - UserStyleSetting.ListUserStyleSetting.ListOption + val listOption = options.value as UserStyleSetting.ListUserStyleSetting.ListOption newWatchFaceData = newWatchFaceData.copy( ambientStyle = AmbientStyle.getAmbientStyleConfig( @@ -393,8 +379,7 @@ class WatchCanvasRenderer( } SECONDS_STYLE_SETTING -> { - val listOption = options.value as - UserStyleSetting.ListUserStyleSetting.ListOption + val listOption = options.value as UserStyleSetting.ListUserStyleSetting.ListOption newWatchFaceData = newWatchFaceData.copy( secondsStyle = SecondsStyle.getSecondsStyleConfig( @@ -404,8 +389,7 @@ class WatchCanvasRenderer( } MILITARY_TIME_SETTING -> { - val booleanValue = options.value as - UserStyleSetting.BooleanUserStyleSetting.BooleanOption + val booleanValue = options.value as UserStyleSetting.BooleanUserStyleSetting.BooleanOption newWatchFaceData = newWatchFaceData.copy( militaryTime = booleanValue.value, @@ -413,8 +397,7 @@ class WatchCanvasRenderer( } BIG_AMBIENT_SETTING -> { - val booleanValue = options.value as - UserStyleSetting.BooleanUserStyleSetting.BooleanOption + val booleanValue = options.value as UserStyleSetting.BooleanUserStyleSetting.BooleanOption newWatchFaceData = newWatchFaceData.copy( bigAmbient = booleanValue.value, @@ -422,8 +405,7 @@ class WatchCanvasRenderer( } DETAILED_AMBIENT_SETTING -> { - val booleanValue = options.value as - UserStyleSetting.BooleanUserStyleSetting.BooleanOption + val booleanValue = options.value as UserStyleSetting.BooleanUserStyleSetting.BooleanOption newWatchFaceData = newWatchFaceData.copy( detailedAmbient = booleanValue.value, @@ -536,10 +518,7 @@ class WatchCanvasRenderer( } override fun renderHighlightLayer( - canvas: Canvas, - bounds: Rect, - zonedDateTime: ZonedDateTime, - sharedAssets: AnalogSharedAssets + canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime, sharedAssets: AnalogSharedAssets ) { canvas.drawColor(renderParameters.highlightLayer!!.backgroundTint) @@ -556,8 +535,8 @@ class WatchCanvasRenderer( // 16f / 14f - 1f // } - private val scale - get() = if (!isHeadless) drawProperties.timeScale else 1f + private val scale + get() = if (!isHeadless) drawProperties.timeScale else 1f fun interpolate(start: Float, end: Float): Float { return start + easeInOutCubic(scale) * (end - start) @@ -566,7 +545,7 @@ class WatchCanvasRenderer( val timeTextSize: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { - 18f; + 18f } else -> { @@ -599,7 +578,7 @@ class WatchCanvasRenderer( if (watchFaceData.detailedAmbient) { -34f - 11f } else { - interpolate(0f, -34f- 11f) + interpolate(0f, -34f - 11f) } } @@ -611,11 +590,11 @@ class WatchCanvasRenderer( val hourOffsetY: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { - 183f; + 183f } else -> { - 185f; + 185f // interpolate(183f, 185f) } } @@ -643,13 +622,13 @@ class WatchCanvasRenderer( } else -> { - 297f; + 297f // interpolate(327f, 297f) } } val shouldDrawSeconds: Boolean - get() = when(watchFaceData.layoutStyle.id) { + get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.INFO1.id -> true LayoutStyle.INFO3.id -> true LayoutStyle.SPORT.id -> true @@ -678,19 +657,19 @@ class WatchCanvasRenderer( } val secondsOffsetY: Float - get() = when(watchFaceData.layoutStyle.id) { + get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> -31f else -> 0f } val shouldDrawAmPm: Boolean - get() = when(watchFaceData.layoutStyle.id) { + get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> true else -> false } val secondsTextSize: Float - get() = when(watchFaceData.layoutStyle.id) { + get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.INFO1.id -> 6f LayoutStyle.INFO3.id -> 6f LayoutStyle.SPORT.id -> 7f @@ -764,7 +743,7 @@ class WatchCanvasRenderer( getHour(zonedDateTime), hourPaint, timeOffsetX, - -7f-49f, + -7f - 49f, timeTextSize, ) @@ -774,7 +753,7 @@ class WatchCanvasRenderer( zonedDateTime.minute, minutePaint, timeOffsetX, - 7f+49f, + 7f + 49f, timeTextSize, ) @@ -903,7 +882,7 @@ class WatchCanvasRenderer( // if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && // watchFaceData.detailedAmbient || drawProperties.timeScale != 0f // ) { - drawComplications(canvas, zonedDateTime) + drawComplications(canvas, zonedDateTime) // } // if (renderParameters.watchFaceLayesr.contains(WatchFaceLayer.COMPLICATIONS) && @@ -923,41 +902,36 @@ class WatchCanvasRenderer( // ----- All drawing functions ----- private fun drawComplications(canvas: Canvas, zonedDateTime: ZonedDateTime) { - var opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) + var opacity = + if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( + drawProperties.timeScale + ) opacity = 1f - val offsetX = if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id) interpolate(34f, 0f) else 0f + val offsetX = + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id) interpolate(34f, 0f) else 0f val scale = interpolate(16f / 14f, 1f) for ((_, complication) in complicationSlotsManager.complicationSlots) { if (complication.enabled) { when (complication.renderer) { is VerticalComplication -> { - (complication.renderer as VerticalComplication).opacity = - opacity - (complication.renderer as VerticalComplication).scale = - scale + (complication.renderer as VerticalComplication).opacity = opacity + (complication.renderer as VerticalComplication).scale = scale } is HorizontalComplication -> { - (complication.renderer as HorizontalComplication).opacity = - opacity - (complication.renderer as HorizontalComplication).offsetX = - offsetX - (complication.renderer as HorizontalComplication).scale = - scale + (complication.renderer as HorizontalComplication).opacity = opacity + (complication.renderer as HorizontalComplication).offsetX = offsetX + (complication.renderer as HorizontalComplication).scale = scale } is HorizontalTextComplication -> { - (complication.renderer as HorizontalTextComplication).opacity = - opacity - (complication.renderer as HorizontalTextComplication).offsetX = - offsetX - (complication.renderer as HorizontalTextComplication).scale = - scale + (complication.renderer as HorizontalTextComplication).opacity = opacity + (complication.renderer as HorizontalTextComplication).offsetX = offsetX + (complication.renderer as HorizontalTextComplication).scale = scale } - is IconComplication -> (complication.renderer as IconComplication).opacity = - opacity + is IconComplication -> (complication.renderer as IconComplication).opacity = opacity else -> {} } @@ -979,18 +953,21 @@ class WatchCanvasRenderer( ) { val text = time.toString().padStart(2, '0') - var opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) + var opacity = + if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( + drawProperties.timeScale + ) opacity = 1f val bitmap = renderSeconds(text, paint, textSize, watchFaceColors.tertiaryColor, opacity) canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawBitmap( - bitmap, - 192f - bitmap.width/2 + offsetX, - 192f - bitmap.height/2 + offsetY, - Paint(), - ) + canvas.drawBitmap( + bitmap, + 192f - bitmap.width / 2 + offsetX, + 192f - bitmap.height / 2 + offsetY, + Paint(), + ) } } @@ -1000,7 +977,7 @@ class WatchCanvasRenderer( textSize: Float, color: Int, opacity: Float, - ): Bitmap { + ): Bitmap { val key = "${text},${textSize},${color},${opacity}" val cached = secondsBitmapCache.get(key) @@ -1017,9 +994,7 @@ class WatchCanvasRenderer( val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) val bitmap = Bitmap.createBitmap( - bounds.width(), - bounds.height(), - Bitmap.Config.ARGB_8888 + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 ) val canvas = Canvas(bitmap) @@ -1060,7 +1035,10 @@ class WatchCanvasRenderer( ) { val text = ampm.uppercase() - var opacity = if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale)/4 else this.easeInOutCirc(drawProperties.timeScale) + var opacity = + if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( + drawProperties.timeScale + ) opacity = 1f val p = Paint(paint) @@ -1073,9 +1051,7 @@ class WatchCanvasRenderer( // scale = 1f val cacheBitmap = Bitmap.createBitmap( - bounds.width(), - bounds.height(), - Bitmap.Config.ARGB_8888 + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 ) val bitmapCanvas = Canvas(cacheBitmap) @@ -1086,7 +1062,7 @@ class WatchCanvasRenderer( bitmapCanvas.drawText( text, 192f, - 192f+textBounds.height()/2, + 192f + textBounds.height() / 2, p, ) @@ -1123,9 +1099,7 @@ class WatchCanvasRenderer( scale *= textSize / 14f val cacheBitmap = Bitmap.createBitmap( - bounds.width(), - bounds.height(), - Bitmap.Config.ARGB_8888 + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 ) val bitmapCanvas = Canvas(cacheBitmap) @@ -1134,8 +1108,8 @@ class WatchCanvasRenderer( bitmapCanvas.drawText( text, - 192f-textBounds.width().toFloat()/2f, - 192f+textBounds.height()/2+offsetY, + 192f - textBounds.width().toFloat() / 2f, + 192f + textBounds.height() / 2 + offsetY, p, ) @@ -1163,14 +1137,14 @@ class WatchCanvasRenderer( ) { val p = Paint(paint) // p.textSize = p.textSize // / 384F * bounds.width() - p.textSize = 14f*8f + p.textSize = 14f * 8f p.isSubpixelText = true p.isAntiAlias = true // p.textSize = 14f var scale = 1f - scale = scale * (textSize/14f) + scale = scale * (textSize / 14f) val text = time.toString().padStart(2, '0') @@ -1180,9 +1154,7 @@ class WatchCanvasRenderer( val cacheBitmap = Bitmap.createBitmap( - 384, - 384, - Bitmap.Config.ARGB_8888 + 384, 384, Bitmap.Config.ARGB_8888 ) val bitmapCanvas = Canvas(cacheBitmap) @@ -1201,7 +1173,6 @@ class WatchCanvasRenderer( } - // val resources: Resources = context.resources // val density = resources.displayMetrics.density //// var bitmap = BitmapFactory.decodeResource(resources, 0) @@ -1305,15 +1276,11 @@ class WatchCanvasRenderer( var scale = 1f when { - watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id - && watchFaceData.secondsStyle.id == SecondsStyle.NONE.id - -> { + watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id && watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> { scale = scale / 14f * 18f } - watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id - && (watchFaceData.secondsStyle.id == SecondsStyle.DASHES.id || watchFaceData.secondsStyle.id == SecondsStyle.DOTS.id) - -> { + watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id && (watchFaceData.secondsStyle.id == SecondsStyle.DASHES.id || watchFaceData.secondsStyle.id == SecondsStyle.DOTS.id) -> { scale = scale / 14f * 18f } } @@ -1333,17 +1300,14 @@ class WatchCanvasRenderer( } private fun drawDashes( - canvas: Canvas, - bounds: Rect, - zonedDateTime: ZonedDateTime + canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime ) { // Retrieve current time to calculate location/rotation of watch arms. val nanoOfDate = zonedDateTime.toLocalTime().toNanoOfDay() val secondsPerSecondHandRotation = Duration.ofMinutes(1).seconds val secondsRotation = - (nanoOfDate.toFloat() / 1000000000F).rem(secondsPerSecondHandRotation) * 360.0f / - secondsPerSecondHandRotation + (nanoOfDate.toFloat() / 1000000000F).rem(secondsPerSecondHandRotation) * 360.0f / secondsPerSecondHandRotation val secondHandSize = 12f * this.easeInOutCirc(this.drawProperties.timeScale) @@ -1461,8 +1425,7 @@ class WatchCanvasRenderer( transitionOffset / 384F * bounds.width(), bounds.exactCenterX() + weight / 384F * bounds.width(), (size + transitionOffset * 2) / 384F * bounds.width(), - ), - outerElementPaint + ), outerElementPaint ) } @@ -1471,17 +1434,14 @@ class WatchCanvasRenderer( } private fun drawDots( - canvas: Canvas, - bounds: Rect, - zonedDateTime: ZonedDateTime + canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime ) { // Retrieve current time to calculate location/rotation of watch arms. val nanoOfDate = zonedDateTime.toLocalTime().toNanoOfDay() val secondsPerSecondHandRotation = Duration.ofMinutes(1).seconds val secondsRotation = - (nanoOfDate.toFloat() / 1000000000F).rem(secondsPerSecondHandRotation) * 360.0f / - secondsPerSecondHandRotation + (nanoOfDate.toFloat() / 1000000000F).rem(secondsPerSecondHandRotation) * 360.0f / secondsPerSecondHandRotation val div = secondsRotation.div(6f).toInt() val mod = secondsRotation.mod(6f) @@ -1632,16 +1592,15 @@ class WatchCanvasRenderer( var timeScale: Float = 0f ) { companion object { - val TIME_SCALE = - object : FloatProperty("timeScale") { - override fun setValue(obj: DrawProperties, value: Float) { - obj.timeScale = value - } + val TIME_SCALE = object : FloatProperty("timeScale") { + override fun setValue(obj: DrawProperties, value: Float) { + obj.timeScale = value + } - override fun get(obj: DrawProperties): Float { - return obj.timeScale - } + override fun get(obj: DrawProperties): Float { + return obj.timeScale } + } } } @@ -1652,15 +1611,13 @@ class WatchCanvasRenderer( fun bitmapCache(canvas: Canvas, bounds: Rect) { val cacheBitmap = Bitmap.createBitmap( - bounds.width(), - bounds.height(), - Bitmap.Config.ARGB_8888 + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 ) val text = "example" val bitmapCanvas = Canvas(cacheBitmap) val bitmapPaint = Paint() bitmapPaint.textSize = 14f - bitmapCanvas.drawText(text, 192f+10f, 192f, bitmapPaint) + bitmapCanvas.drawText(text, 192f + 10f, 192f, bitmapPaint) canvas.withScale(1f, 1f, bounds.exactCenterX(), bounds.exactCenterY()) { canvas.drawBitmap(cacheBitmap, 0f, 0f, Paint()) @@ -1671,7 +1628,7 @@ class BitmapCache { var renders: Int = 0 var loads: Int = 0 private var key: String = "" - private var bitmap: Bitmap? = null; + private var bitmap: Bitmap? = null fun get(k: String): Bitmap? { loads++ From 14828f717beaf317557bc5746d2953d88a2b30a1 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 14 Feb 2024 01:33:51 +0200 Subject: [PATCH 18/52] Bitmap cache for hours/minutes (wip) --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 207 +++++++++++++++--- 1 file changed, 172 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 45b4c6f..b24e771 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -249,7 +249,9 @@ class WatchCanvasRenderer( private var isHeadless = false private var isAmbient = false - private val secondsBitmapCache: BitmapCache = BitmapCache() + private val secondsBitmap: BitmapCache = BitmapCache() + private val hoursBitmap: BitmapCache = BitmapCache() + private val minutesBitmap: BitmapCache = BitmapCache() private val ambientExitAnimator = AnimatorSet().apply { val linearOutSlow = AnimationUtils.loadInterpolator( @@ -552,7 +554,23 @@ class WatchCanvasRenderer( if (watchFaceData.detailedAmbient) { 14f } else { - interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) +// interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) + 14f + } + } + } + + val timeTextScale: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 18f / 18f + } + + else -> { + if (watchFaceData.detailedAmbient) { + 14f / 14f + } else { + interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) } } } @@ -590,11 +608,15 @@ class WatchCanvasRenderer( val hourOffsetY: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { - 183f +// 183f - 192f + -58f - 10f - 4f +// -7f - 49f } else -> { - 185f + -56f +// 185f - 192f +// -7f - 49f // interpolate(183f, 185f) } } @@ -618,11 +640,11 @@ class WatchCanvasRenderer( val minuteOffsetY: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { - 327f + 58f + 10f + 4f } else -> { - 297f + 56f // interpolate(327f, 297f) } } @@ -737,24 +759,26 @@ class WatchCanvasRenderer( canvas.drawColor(Color.parseColor("#ff000000")) if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - drawTime( + drawHours( canvas, bounds, getHour(zonedDateTime), hourPaint, timeOffsetX, - -7f - 49f, + hourOffsetY, timeTextSize, + timeTextScale ) - drawTime( + drawMinutes( canvas, bounds, zonedDateTime.minute, minutePaint, timeOffsetX, - 7f + 49f, + minuteOffsetY, timeTextSize, + timeTextScale ) if (shouldDrawSeconds) { @@ -980,7 +1004,7 @@ class WatchCanvasRenderer( ): Bitmap { val key = "${text},${textSize},${color},${opacity}" - val cached = secondsBitmapCache.get(key) + val cached = secondsBitmap.get(key) if (cached != null) { return cached } @@ -1012,14 +1036,14 @@ class WatchCanvasRenderer( val p2 = Paint() p2.color = Color.WHITE canvas.drawText( - "r ${secondsBitmapCache.loads} w ${secondsBitmapCache.renders}", + "r ${secondsBitmap.loads} w ${secondsBitmap.renders}", 0f, bounds.height().toFloat(), p2, ) // --------------------------- - secondsBitmapCache.set(key, bitmap) + secondsBitmap.set(key, bitmap) return bitmap } @@ -1078,51 +1102,164 @@ class WatchCanvasRenderer( } } - private fun drawTime( + private fun drawHours( canvas: Canvas, bounds: Rect, time: Int, paint: Paint, offsetX: Float, offsetY: Float, - textSize: Float + textSize: Float, + textScale: Float, ) { val text = time.toString().padStart(2, '0') val p = Paint(paint) p.textSize *= 14f - p.isAntiAlias = true - p.isDither = true - p.isFilterBitmap = true +// p.isAntiAlias = true +// p.isDither = true +// p.isFilterBitmap = true - var scale = 1f - scale *= textSize / 14f + val bitmap = renderHours(text, paint, textSize) - val cacheBitmap = Bitmap.createBitmap( + canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.drawBitmap( + bitmap, + 192f - bitmap.width / 2 + offsetX, + 192f - bitmap.height / 2 + offsetY, + Paint(), + ) + } + } + + private fun drawMinutes( + canvas: Canvas, + bounds: Rect, + time: Int, + paint: Paint, + offsetX: Float, + offsetY: Float, + textSize: Float, + textScale: Float, + ) { + val text = time.toString().padStart(2, '0') + + val p = Paint(paint) + p.textSize *= 14f +// p.isAntiAlias = true +// p.isDither = true +// p.isFilterBitmap = true + + val bitmap = renderMinutes(text, paint, textSize) + + canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.drawBitmap( + bitmap, + 192f - bitmap.width / 2 + offsetX, + 192f - bitmap.height / 2 + offsetY, + Paint(), + ) + } + } + + private fun renderHours( + text: String, + paint: Paint, + textSize: Float, + ): Bitmap { + val key = "${text},${textSize}" + + val cached = hoursBitmap.get(key) + if (cached != null) { + return cached + } + + val p = Paint(paint) + p.textSize *= textSize + + val textBounds = Rect() + p.getTextBounds(text, 0, text.length, textBounds) + val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) + + val bitmap = Bitmap.createBitmap( bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 ) - val bitmapCanvas = Canvas(cacheBitmap) + val canvas = Canvas(bitmap) + + canvas.drawText( + text, + 0f, + bounds.height().toFloat(), + p, + ) + + // DEBUG -------------------- + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#22ffffff") + }) + val p2 = Paint() + p2.color = Color.WHITE + canvas.drawText( + "r ${hoursBitmap.loads} w ${hoursBitmap.renders}", + 0f, + bounds.height().toFloat(), + p2, + ) + // --------------------------- + + hoursBitmap.set(key, bitmap) + + return bitmap + } + + private fun renderMinutes( + text: String, + paint: Paint, + textSize: Float, + ): Bitmap { + val key = "${text},${textSize}" + + val cached = minutesBitmap.get(key) + if (cached != null) { + return cached + } + + val p = Paint(paint) + p.textSize *= textSize val textBounds = Rect() p.getTextBounds(text, 0, text.length, textBounds) + val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) - bitmapCanvas.drawText( + val bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + + canvas.drawText( text, - 192f - textBounds.width().toFloat() / 2f, - 192f + textBounds.height() / 2 + offsetY, + 0f, + bounds.height().toFloat(), p, ) - canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.withTranslation(offsetX, 0f) { - canvas.drawBitmap( - cacheBitmap, - 0f, - 0f, - Paint(), - ) - } - } + // DEBUG -------------------- + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#22ffffff") + }) + val p2 = Paint() + p2.color = Color.WHITE + canvas.drawText( + "r ${minutesBitmap.loads} w ${minutesBitmap.renders}", + 0f, + bounds.height().toFloat(), + p2, + ) + // --------------------------- + + minutesBitmap.set(key, bitmap) + + return bitmap } private fun drawTimeBitmap( From 94a62bd0a1592283032d75e7c0a9542997dcd79e Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 14 Feb 2024 23:39:42 +0200 Subject: [PATCH 19/52] Global bitmap cache, merge hours and minutes rendering code --- .../main/java/dev/rdnt/m8face/BitmapCache.kt | 52 +++ .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 337 ++---------------- 2 files changed, 77 insertions(+), 312 deletions(-) create mode 100644 app/src/main/java/dev/rdnt/m8face/BitmapCache.kt diff --git a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt new file mode 100644 index 0000000..7fda36d --- /dev/null +++ b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt @@ -0,0 +1,52 @@ +package dev.rdnt.m8face + +import android.graphics.Bitmap + +const val HOURS_BITMAP_KEY = "hours" +const val MINUTES_BITMAP_KEY = "minutes" +const val SECONDS_BITMAP_KEY = "seconds" + +class BitmapCache { + private val entries: MutableMap = mutableMapOf() + + fun get(k: String, h: String): Bitmap? { + return entries[k]?.get(h) + } + + fun set(k: String, h: String, b: Bitmap?) { + val entry = entries.getOrPut(k) { BitmapCacheEntry() } + entry.set(h, b) + } + + fun renders(k: String): Int { + return entries[k]?.renders ?: 0 + } + + fun loads(k: String): Int { + return entries[k]?.loads ?: 0 + } +} + +class BitmapCacheEntry { + var renders: Int = 0 + var loads: Int = 0 + private var hash: String = "" + private var bitmap: Bitmap? = null + + fun get(h: String): Bitmap? { + loads++ + + if (bitmap != null && h == hash) { + return bitmap + } + + return null + } + + fun set(h: String, b: Bitmap?) { + renders++ + + hash = h + bitmap = b + } +} diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index b24e771..1c04b25 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -33,6 +33,7 @@ import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep import androidx.core.graphics.ColorUtils +import androidx.core.graphics.get import androidx.core.graphics.withRotation import androidx.core.graphics.withScale import androidx.core.graphics.withTranslation @@ -74,6 +75,8 @@ import kotlinx.coroutines.launch // Default for how long each frame is displayed at expected frame rate. private const val DEFAULT_INTERACTIVE_DRAW_MODE_UPDATE_DELAY_MILLIS: Long = 16 + + /** * Renders watch face via data in Room database. Also, updates watch face state based on setting * changes by user via [userStyleRepository.addUserStyleListener()]. @@ -249,9 +252,7 @@ class WatchCanvasRenderer( private var isHeadless = false private var isAmbient = false - private val secondsBitmap: BitmapCache = BitmapCache() - private val hoursBitmap: BitmapCache = BitmapCache() - private val minutesBitmap: BitmapCache = BitmapCache() + private val bitmapCache: BitmapCache = BitmapCache() private val ambientExitAnimator = AnimatorSet().apply { val linearOutSlow = AnimationUtils.loadInterpolator( @@ -759,7 +760,7 @@ class WatchCanvasRenderer( canvas.drawColor(Color.parseColor("#ff000000")) if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - drawHours( + drawTime( canvas, bounds, getHour(zonedDateTime), @@ -767,10 +768,11 @@ class WatchCanvasRenderer( timeOffsetX, hourOffsetY, timeTextSize, - timeTextScale + timeTextScale, + HOURS_BITMAP_KEY ) - drawMinutes( + drawTime( canvas, bounds, zonedDateTime.minute, @@ -778,7 +780,8 @@ class WatchCanvasRenderer( timeOffsetX, minuteOffsetY, timeTextSize, - timeTextScale + timeTextScale, + MINUTES_BITMAP_KEY ) if (shouldDrawSeconds) { @@ -795,7 +798,8 @@ class WatchCanvasRenderer( } - if (shouldDrawAmPm) { + if (shouldDrawAmPm && false) { + // TODO drawAmPm( canvas, bounds, @@ -1002,9 +1006,9 @@ class WatchCanvasRenderer( color: Int, opacity: Float, ): Bitmap { - val key = "${text},${textSize},${color},${opacity}" + val hash = "${text},${textSize},${color},${opacity}" - val cached = secondsBitmap.get(key) + val cached = bitmapCache.get(SECONDS_BITMAP_KEY, hash) if (cached != null) { return cached } @@ -1036,14 +1040,14 @@ class WatchCanvasRenderer( val p2 = Paint() p2.color = Color.WHITE canvas.drawText( - "r ${secondsBitmap.loads} w ${secondsBitmap.renders}", + "r ${bitmapCache.loads(SECONDS_BITMAP_KEY)} w ${bitmapCache.renders(SECONDS_BITMAP_KEY)}", 0f, bounds.height().toFloat(), p2, ) // --------------------------- - secondsBitmap.set(key, bitmap) + bitmapCache.set(SECONDS_BITMAP_KEY, hash, bitmap) return bitmap } @@ -1102,7 +1106,7 @@ class WatchCanvasRenderer( } } - private fun drawHours( + private fun drawTime( canvas: Canvas, bounds: Rect, time: Int, @@ -1111,16 +1115,14 @@ class WatchCanvasRenderer( offsetY: Float, textSize: Float, textScale: Float, + cacheKey: String, ) { val text = time.toString().padStart(2, '0') val p = Paint(paint) p.textSize *= 14f -// p.isAntiAlias = true -// p.isDither = true -// p.isFilterBitmap = true - val bitmap = renderHours(text, paint, textSize) + val bitmap = renderTime(text, paint, textSize, cacheKey) canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { canvas.drawBitmap( @@ -1132,94 +1134,15 @@ class WatchCanvasRenderer( } } - private fun drawMinutes( - canvas: Canvas, - bounds: Rect, - time: Int, - paint: Paint, - offsetX: Float, - offsetY: Float, - textSize: Float, - textScale: Float, - ) { - val text = time.toString().padStart(2, '0') - - val p = Paint(paint) - p.textSize *= 14f -// p.isAntiAlias = true -// p.isDither = true -// p.isFilterBitmap = true - - val bitmap = renderMinutes(text, paint, textSize) - - canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawBitmap( - bitmap, - 192f - bitmap.width / 2 + offsetX, - 192f - bitmap.height / 2 + offsetY, - Paint(), - ) - } - } - - private fun renderHours( - text: String, - paint: Paint, - textSize: Float, - ): Bitmap { - val key = "${text},${textSize}" - - val cached = hoursBitmap.get(key) - if (cached != null) { - return cached - } - - val p = Paint(paint) - p.textSize *= textSize - - val textBounds = Rect() - p.getTextBounds(text, 0, text.length, textBounds) - val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) - - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - val canvas = Canvas(bitmap) - - canvas.drawText( - text, - 0f, - bounds.height().toFloat(), - p, - ) - - // DEBUG -------------------- - canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#22ffffff") - }) - val p2 = Paint() - p2.color = Color.WHITE - canvas.drawText( - "r ${hoursBitmap.loads} w ${hoursBitmap.renders}", - 0f, - bounds.height().toFloat(), - p2, - ) - // --------------------------- - - hoursBitmap.set(key, bitmap) - - return bitmap - } - - private fun renderMinutes( + private fun renderTime( text: String, paint: Paint, textSize: Float, + cacheKey: String, ): Bitmap { - val key = "${text},${textSize}" + val hash = "${text},${textSize}" - val cached = minutesBitmap.get(key) + val cached = bitmapCache.get(cacheKey, hash) if (cached != null) { return cached } @@ -1250,192 +1173,18 @@ class WatchCanvasRenderer( val p2 = Paint() p2.color = Color.WHITE canvas.drawText( - "r ${minutesBitmap.loads} w ${minutesBitmap.renders}", + "r ${bitmapCache.loads(cacheKey)} w ${bitmapCache.renders(cacheKey)}", 0f, bounds.height().toFloat(), p2, ) // --------------------------- - minutesBitmap.set(key, bitmap) + bitmapCache.set(cacheKey, hash, bitmap) return bitmap } - private fun drawTimeBitmap( - canvas: Canvas, - bounds: Rect, - time: Int, - paint: Paint, - offsetX: Float, - offsetY: Float, - textSize: Float, -// timeScale: Float - ) { - val p = Paint(paint) -// p.textSize = p.textSize // / 384F * bounds.width() - p.textSize = 14f * 8f - p.isSubpixelText = true - p.isAntiAlias = true -// p.textSize = 14f - - var scale = 1f - - scale = scale * (textSize / 14f) - - - val text = time.toString().padStart(2, '0') - - val textBounds = Rect() - p.getTextBounds(text, 0, text.length, textBounds) - - - val cacheBitmap = Bitmap.createBitmap( - 384, 384, Bitmap.Config.ARGB_8888 - ) - val bitmapCanvas = Canvas(cacheBitmap) - - val bitmapPaint = Paint(paint) - bitmapPaint.textSize *= 14f -// bitmapPaint.color = Color.WHITE -// bitmapPaint.isAntiAlias = true -// bitmapPaint.isDither = true -// bitmapPaint.isFilterBitmap = true - - -// bitmapCanvas.drawText(text, 0f, 0f, bitmapPaint) - - bitmapCanvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { - bitmapCanvas.drawText(text, 192f, 192f, bitmapPaint) - } - - -// val resources: Resources = context.resources -// val density = resources.displayMetrics.density -//// var bitmap = BitmapFactory.decodeResource(resources, 0) -// -// var bitmap = Bitmap.createBitmap(textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888); -// -// -// val bitmapConfig = bitmap.config -// -// // set default bitmap config if none -//// if(bitmapConfig == null) { -//// bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888; -//// } -//// val drawable = data.smallImage.image.loadDrawable(context) ?: return -// -// -// bitmap = bitmap.copy(bitmapConfig, true) -// -//// val bitmapCanvas = Canvas(bitmap) -// -// val x = (bitmap.width - textBounds.width()) / 2 -// val y = (bitmap.height + textBounds.height()) / 2 -// -// -// val paint2 = Paint(Paint.ANTI_ALIAS_FLAG) -// // text color - #3D3D3D -// // text color - #3D3D3D -// paint2.color = Color.rgb(61, 61, 61) -// // text size in pixels -// // text size in pixels -// paint2.textSize = (14 * scale).toInt().toFloat() -// // text shadow -// // text shadow -// paint2.setShadowLayer(1f, 0f, 1f, Color.WHITE) -// -// bitmapCanvas.drawText(text, 0f, 0f, paint2) - - -// iconBounds = Rect(0, 0, 0, size) - -// val dstRect = RectF( -// bounds.exactCenterX() - textBounds.width() / 2, -// bounds.exactCenterY() - textBounds.height() / 2, -// bounds.exactCenterX() + textBounds.width() / 2, -// bounds.exactCenterY() + textBounds.height() / 2, - val imgPaint = Paint() -// imgPaint.color = Color.WHITE -// imgPaint.isAntiAlias = true -// imgPaint.isDither = true -// imgPaint.isFilterBitmap = true - - val colorMatrix = ColorMatrix() - colorMatrix.setSaturation(0f) - val filter = ColorMatrixColorFilter(colorMatrix) - imgPaint.colorFilter = filter - -// p.isDither = true -// p.isFilterBitmap = true -// p.isAntiAlias = true - -// ) - canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawText(text, 192f, 192f, p) - } - - canvas.withTranslation(0f, 0f) { -// canvas.drawBitmap(cacheBitmap, 0f, 0f, imgPaint) -// canvas.drawText(text, 192f, 192f, p) - } -// canvas.withTranslation(offsetX, offsetY) { - -// canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { -// canvas.withTranslation(-77f, -7f) { -// canvas.drawText( -// time.toString().padStart(2, '0'), -// 192f, 192f, -// // offsetX, -// // offsetY, -// p -// ) -// } -// } -// } - } - - private fun drawTimeOld( - canvas: Canvas, - bounds: Rect, - time: Int, - paint: Paint, - offsetX: Float, - offsetY: Float, - scaleOffset: Float - ) { - return - - // dx:70 dy:98 - val p = Paint(paint) - p.textSize = p.textSize / 384F * bounds.width() - - var scale = 1f - - when { - watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id && watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> { - scale = scale / 14f * 18f - } - - watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id && (watchFaceData.secondsStyle.id == SecondsStyle.DASHES.id || watchFaceData.secondsStyle.id == SecondsStyle.DOTS.id) -> { - scale = scale / 14f * 18f - } - } - - p.isAntiAlias = true - - scale += (1f - this.easeInOutCirc(drawProperties.timeScale)) * scaleOffset - - canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawText( - time.toString().padStart(2, '0'), - bounds.exactCenterX() + offsetX / 384F * bounds.width().toFloat(), - bounds.exactCenterY() + offsetY / 384F * bounds.height().toFloat(), - p - ) - } - } - private fun drawDashes( canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime ) { @@ -1746,39 +1495,3 @@ class WatchCanvasRenderer( } } -fun bitmapCache(canvas: Canvas, bounds: Rect) { - val cacheBitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - val text = "example" - val bitmapCanvas = Canvas(cacheBitmap) - val bitmapPaint = Paint() - bitmapPaint.textSize = 14f - bitmapCanvas.drawText(text, 192f + 10f, 192f, bitmapPaint) - - canvas.withScale(1f, 1f, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawBitmap(cacheBitmap, 0f, 0f, Paint()) - } -} - -class BitmapCache { - var renders: Int = 0 - var loads: Int = 0 - private var key: String = "" - private var bitmap: Bitmap? = null - - fun get(k: String): Bitmap? { - loads++ - if (bitmap != null && k == key) { - return bitmap - } - - return null - } - - fun set(k: String, b: Bitmap?) { - renders++ - key = k - bitmap = b - } -} From c46cd517de3b4c75cdaa8d199f0619bcb0618022 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Thu, 15 Feb 2024 00:09:11 +0200 Subject: [PATCH 20/52] Fix am/pm rendering, seconds offsets --- .../main/java/dev/rdnt/m8face/BitmapCache.kt | 1 + .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 132 ++++++++++++++---- 2 files changed, 104 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt index 7fda36d..5f069f3 100644 --- a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt +++ b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt @@ -5,6 +5,7 @@ import android.graphics.Bitmap const val HOURS_BITMAP_KEY = "hours" const val MINUTES_BITMAP_KEY = "minutes" const val SECONDS_BITMAP_KEY = "seconds" +const val AMPM_BITMAP_KEY = "ampm" class BitmapCache { private val entries: MutableMap = mutableMapOf() diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 1c04b25..f943efb 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -691,6 +691,13 @@ class WatchCanvasRenderer( else -> false } + val ampmOffsetX: Float + get() = if (watchFaceData.detailedAmbient) { + 84f + } else { + interpolate(84f+34f, 84f) + } + val secondsTextSize: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.INFO1.id -> 6f @@ -797,15 +804,14 @@ class WatchCanvasRenderer( ) } - - if (shouldDrawAmPm && false) { + if (shouldDrawAmPm) { // TODO drawAmPm( canvas, bounds, getAmPm(zonedDateTime), secondPaint, - timeOffsetX, + ampmOffsetX, 25f, ampmTextScale, ) @@ -1059,7 +1065,7 @@ class WatchCanvasRenderer( paint: Paint, offsetX: Float, offsetY: Float, - _scale: Float, + textScale: Float, ) { val text = ampm.uppercase() @@ -1069,41 +1075,109 @@ class WatchCanvasRenderer( ) opacity = 1f - val p = Paint(paint) - p.textSize *= 5f - p.isAntiAlias = true - p.isDither = true - p.isFilterBitmap = true + val bitmap = renderAmPm(text, paint, 5f, AMPM_BITMAP_KEY) - var scale = _scale / 14f -// scale = 1f + canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.drawBitmap( + bitmap, + 192f - bitmap.width / 2 + offsetX, + 192f - bitmap.height / 2 + offsetY, + Paint(), + ) + } - val cacheBitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - val bitmapCanvas = Canvas(cacheBitmap) + + + + + + +// val p = Paint(paint) +//// p.textSize *= 5f +//// p.isAntiAlias = true +//// p.isDither = true +//// p.isFilterBitmap = true +// +// var scale = _scale / 14f +//// scale = 1f +// +// val cacheBitmap = Bitmap.createBitmap( +// bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 +// ) +// val bitmapCanvas = Canvas(cacheBitmap) +// +// val textBounds = Rect() +// p.getTextBounds(text, 0, text.length, textBounds) +// p.color = ColorUtils.blendARGB(Color.TRANSPARENT, watchFaceColors.tertiaryColor, opacity) +// +// bitmapCanvas.drawText( +// text, +// 192f, +// 192f + textBounds.height() / 2, +// p, +// ) +// +// canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { +// canvas.withTranslation(offsetX, offsetY) { +// canvas.drawBitmap( +// cacheBitmap, +// 0f, +// 0f, +// Paint(), +// ) +// } +// } + } + + private fun renderAmPm( + text: String, + paint: Paint, + textSize: Float, + cacheKey: String, + ): Bitmap { + val hash = "${text},${textSize}" + + val cached = bitmapCache.get(cacheKey, hash) + if (cached != null) { + return cached + } + + val p = Paint(paint) + p.textSize *= textSize val textBounds = Rect() p.getTextBounds(text, 0, text.length, textBounds) - p.color = ColorUtils.blendARGB(Color.TRANSPARENT, watchFaceColors.tertiaryColor, opacity) + val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) - bitmapCanvas.drawText( + val bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + + canvas.drawText( text, - 192f, - 192f + textBounds.height() / 2, + 0f, + bounds.height().toFloat(), p, ) - canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.withTranslation(offsetX, offsetY) { - canvas.drawBitmap( - cacheBitmap, - 0f, - 0f, - Paint(), - ) - } - } + // DEBUG -------------------- + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#22ffffff") + }) + val p2 = Paint() + p2.color = Color.WHITE + canvas.drawText( + "r ${bitmapCache.loads(cacheKey)} w ${bitmapCache.renders(cacheKey)}", + 0f, + bounds.height().toFloat(), + p2, + ) + // --------------------------- + + bitmapCache.set(cacheKey, hash, bitmap) + + return bitmap } private fun drawTime( From 40dc31dbb8fe70ea50b504c581ef306ddee460eb Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Thu, 15 Feb 2024 00:19:10 +0200 Subject: [PATCH 21/52] Update gradle --- app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 1 - gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index f943efb..3300f10 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -805,7 +805,6 @@ class WatchCanvasRenderer( } if (shouldDrawAmPm) { - // TODO drawAmPm( canvas, bounds, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 651071e..c103419 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanist-pager = "0.32.0" activity-compose = "1.8.0" -android-gradle-plugin = "8.1.2" +android-gradle-plugin = "8.2.2" androidx-activity = "1.8.0" androidx-lifecycle = "2.6.2" androidx-wear-watchface = "1.1.1" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2848974..8612e3f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 415e1c12506e0ae57c83272bf7b07c538cb45786 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Thu, 15 Feb 2024 22:18:36 +0200 Subject: [PATCH 22/52] Unify text rendering functions, cleanup debug text --- app/build.gradle | 1 + .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 526 +++++------------- .../m8face/editor/WatchFaceConfigActivity.kt | 8 +- .../rdnt/m8face/utils/ComplicationUtils.kt | 4 +- 4 files changed, 151 insertions(+), 388 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 818d6dd..a39ff0b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,6 +51,7 @@ android { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' versionNameSuffix '-preview' + signingConfig signingConfigs.debug } release { diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 3300f10..3037ed2 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -16,27 +16,24 @@ package dev.rdnt.m8face import android.animation.AnimatorSet -import android.animation.Keyframe import android.animation.ObjectAnimator -import android.animation.PropertyValuesHolder import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color -import android.graphics.ColorMatrix -import android.graphics.ColorMatrixColorFilter import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF import android.util.FloatProperty +import android.util.Log import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep +import androidx.core.animation.doOnEnd +import androidx.core.animation.doOnStart import androidx.core.graphics.ColorUtils -import androidx.core.graphics.get import androidx.core.graphics.withRotation import androidx.core.graphics.withScale -import androidx.core.graphics.withTranslation import androidx.wear.watchface.ComplicationSlotsManager import androidx.wear.watchface.Renderer import androidx.wear.watchface.WatchState @@ -148,7 +145,7 @@ class WatchCanvasRenderer( isAntiAlias = true // make sure text is not anti-aliased even with this on typeface = context.resources.getFont(R.font.m8stealth57) // textSize = 112f / 14f * 14f // TODO: 98f/112f - textSize = 112f / 14f + textSize = 8f color = watchFaceColors.primaryColor } @@ -186,7 +183,7 @@ class WatchCanvasRenderer( isAntiAlias = true // make sure text is not anti-aliased even with this on typeface = context.resources.getFont(R.font.m8stealth57) // textSize = 112f / 14f * 14f // TODO: 98f/112F - textSize = 112f / 14f + textSize = 8f color = watchFaceColors.secondaryColor } @@ -254,6 +251,8 @@ class WatchCanvasRenderer( private val bitmapCache: BitmapCache = BitmapCache() + private var interactiveFrameDelay: Long = 60000 + private val ambientExitAnimator = AnimatorSet().apply { val linearOutSlow = AnimationUtils.loadInterpolator( context, android.R.interpolator.linear_out_slow_in @@ -265,6 +264,16 @@ class WatchCanvasRenderer( duration = ambientTransitionMs interpolator = linearOutSlow setAutoCancel(false) + doOnStart { +// updateRefreshRate() + interactiveDrawModeUpdateDelayMillis = 16 + Log.d("@@@", "START") + } + doOnEnd { + interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay +// updateRefreshRate() + Log.d("@@@", "END") + } }, ) } @@ -273,24 +282,48 @@ class WatchCanvasRenderer( val linearOutSlow = AnimationUtils.loadInterpolator( context, android.R.interpolator.linear_out_slow_in ) - - val keyframes = arrayOf( - Keyframe.ofFloat(0f, drawProperties.timeScale), - Keyframe.ofFloat(0.9f, 0f), - Keyframe.ofFloat(1f, 0f) - ) - - val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( - DrawProperties.TIME_SCALE, *keyframes - ) - play( - ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { - duration = ambientTransitionMs * 5 / 9 + ObjectAnimator.ofFloat( + drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale, 0.0f + ).apply { + duration = ambientTransitionMs interpolator = linearOutSlow setAutoCancel(false) + doOnStart { +// updateRefreshRate() + interactiveDrawModeUpdateDelayMillis = 16 + Log.d("@@@", "START") + } + doOnEnd { + interactiveDrawModeUpdateDelayMillis = 60000 +// updateRefreshRate() + Log.d("@@@", "END") + } }, ) + +// val linearOutSlow = AnimationUtils.loadInterpolator( +// context, android.R.interpolator.linear_out_slow_in +// ) +// +// val keyframes = arrayOf( +// Keyframe.ofFloat(0f, drawProperties.timeScale), +// Keyframe.ofFloat(0.9f, 0f), +// Keyframe.ofFloat(1f, 0f) +// ) +// +// val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( +// DrawProperties.TIME_SCALE, *keyframes +// ) +// +// play( +// ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { +//// duration = ambientTransitionMs * 5 / 9 +// duration = ambientTransitionMs +// interpolator = linearOutSlow +// setAutoCancel(false) +// }, +// ) } init { @@ -307,11 +340,13 @@ class WatchCanvasRenderer( if (!watchState.isHeadless) { if (isAmbient) { + ambientExitAnimator.cancel() ambientEnterAnimator.setupStartValues() ambientEnterAnimator.start() // ambientExitAnimator.cancel() // drawProperties.timeScale = 0f } else { + ambientEnterAnimator.cancel() ambientExitAnimator.setupStartValues() ambientExitAnimator.start() } @@ -319,6 +354,8 @@ class WatchCanvasRenderer( ambientExitAnimator.setupStartValues() drawProperties.timeScale = 0f } + +// updateRefreshRate() } } } @@ -327,16 +364,16 @@ class WatchCanvasRenderer( return AnalogSharedAssets() } - private fun updateRefreshRate() { - interactiveDrawModeUpdateDelayMillis = when { - animating() -> 16 // 60 fps cause animating -// watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 60000 // update once a second - watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 1000 // update once a second // TODO: handle digital seconds - watchFaceData.secondsStyle.id == SecondsStyle.DASHES.id -> 16 // 60 fps - watchFaceData.secondsStyle.id == SecondsStyle.DOTS.id -> 16 // 60 fps - else -> 60000 // safe default - } - } +// private fun updateRefreshRate() { +// interactiveDrawModeUpdateDelayMillis = when { +// ambientEnterAnimator.isRunning || ambientExitAnimator.isRunning -> 19 // TODO // 60 fps cause animating +//// watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 60000 // update once a second +// watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 1000 // update once a second // TODO: handle digital seconds +// watchFaceData.secondsStyle.id == SecondsStyle.DASHES.id -> 17 // 60 fps TODO +// watchFaceData.secondsStyle.id == SecondsStyle.DOTS.id -> 18 // 60 fps TODO +// else -> 60000 // safe default +// } +// } /* * Triggered when the user makes changes to the watch face through the settings activity. The @@ -488,6 +525,14 @@ class WatchCanvasRenderer( is24Format = watchFaceData.militaryTime + interactiveFrameDelay = when (watchFaceData.secondsStyle.id) { + SecondsStyle.NONE.id -> if (shouldDrawSeconds) 1000 else 60000 + SecondsStyle.DASHES.id -> 16 + SecondsStyle.DOTS.id -> 16 + else -> 1000 + } + interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay + // TODO: update colors for all elements here // Applies the user chosen complication color scheme changes. ComplicationDrawables for @@ -681,7 +726,7 @@ class WatchCanvasRenderer( val secondsOffsetY: Float get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> -31f + LayoutStyle.SPORT.id -> -32f else -> 0f } @@ -722,18 +767,10 @@ class WatchCanvasRenderer( } val ampmTextScale: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 18f / 14f - } - - else -> { - if (watchFaceData.detailedAmbient) { - 14f / 14f - } else { - interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) - } - } + get() = if (watchFaceData.detailedAmbient) { + 14f / 14f + } else { + interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) } fun getHour(zonedDateTime: ZonedDateTime): Int { @@ -756,22 +793,34 @@ class WatchCanvasRenderer( } } + private var renders: Int = 0 + override fun render( canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime, sharedAssets: AnalogSharedAssets, ) { - updateRefreshRate() + renders++ canvas.drawColor(Color.parseColor("#ff000000")) +// val p2 = Paint() +// p2.color = Color.WHITE +// canvas.drawText( +// "frame $renders ${ambientEnterAnimator.isRunning} ${ambientExitAnimator.isRunning} ${interactiveDrawModeUpdateDelayMillis}", +// 192f, +// bounds.height().toFloat()-192f, +// p2, +// ) + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - drawTime( + drawText( canvas, bounds, - getHour(zonedDateTime), + getHour(zonedDateTime).toString().padStart(2, '0'), hourPaint, + 1f, timeOffsetX, hourOffsetY, timeTextSize, @@ -779,11 +828,12 @@ class WatchCanvasRenderer( HOURS_BITMAP_KEY ) - drawTime( + drawText( canvas, bounds, - zonedDateTime.minute, + zonedDateTime.minute.toString().padStart(2, '0'), minutePaint, + 1f, timeOffsetX, minuteOffsetY, timeTextSize, @@ -792,122 +842,55 @@ class WatchCanvasRenderer( ) if (shouldDrawSeconds) { - drawSeconds( + var opacity = + if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( + drawProperties.timeScale + ) +// opacity = 1f + + drawText( canvas, bounds, - zonedDateTime.second, + zonedDateTime.second.toString().padStart(2, '0'), secondPaint, + opacity, secondsOffsetX, secondsOffsetY, secondsTextSize, secondsTextScale, + SECONDS_BITMAP_KEY, ) } if (shouldDrawAmPm) { - drawAmPm( + var opacity = + if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( + drawProperties.timeScale + ) +// opacity = 1f + + drawText( canvas, bounds, - getAmPm(zonedDateTime), + getAmPm(zonedDateTime).uppercase(), secondPaint, + opacity, ampmOffsetX, - 25f, + 24f, + 5f, ampmTextScale, + AMPM_BITMAP_KEY, ) } - -// if (drawProperties.timeScale == 0f) { -// var hourOffsetX = 0f -// var hourOffsetY = 0f -// var minuteOffsetX = 0f -// var minuteOffsetY = 0f -// -// when (watchFaceData.ambientStyle.id) { -// AmbientStyle.OUTLINE.id -> { -// if (watchFaceData.bigAmbient) { -// hourOffsetX = -99f -// hourOffsetY = -9f -// minuteOffsetX = -99f -// minuteOffsetY = 135f -// } else { -// hourOffsetX = -88f -// hourOffsetY = -8f -// minuteOffsetX = -88f -// minuteOffsetY = 120f -// } -// } -// -// AmbientStyle.BOLD_OUTLINE.id -> { -// if (watchFaceData.bigAmbient) { -// hourOffsetX = -99f -// hourOffsetY = -9f -// minuteOffsetX = -99f -// minuteOffsetY = 135f -// } else { -// hourOffsetX = -88f -// hourOffsetY = -8f -// minuteOffsetX = -88f -// minuteOffsetY = 120f -// } -// } -// -// AmbientStyle.FILLED.id -> { -// if (watchFaceData.bigAmbient) { -// hourOffsetX = -99f -// hourOffsetY = -9f -// minuteOffsetX = -99f -// minuteOffsetY = 135f -// } else { -// hourOffsetX = -88f -// hourOffsetY = -8f -// minuteOffsetX = -88f -// minuteOffsetY = 120f -// } -// } -// } -// -//// drawTimeOld(canvas, bounds, hour, ambientHourPaint, hourOffsetX, hourOffsetY, 0f) -//// drawTimeOld( -//// canvas, -//// bounds, -//// zonedDateTime.minute, -//// ambientMinutePaint, -//// minuteOffsetX, -//// minuteOffsetY, -//// 0f -//// ) -// } else { -// when (watchFaceData.secondsStyle.id) { -// SecondsStyle.NONE.id -> { -// -// } -// -// SecondsStyle.DASHES.id -> { -// drawDashes(canvas, bounds, zonedDateTime) -// } -// -// SecondsStyle.DOTS.id -> { -// drawDots(canvas, bounds, zonedDateTime) -// } +// when (watchFaceData.secondsStyle.id) { +// SecondsStyle.DASHES.id -> { +// drawDashes(canvas, bounds, zonedDateTime) // } // -// val scaleOffset = if (this.watchFaceData.bigAmbient) { -// 18f / 14f - 1f -// } else { -// 16f / 14f - 1f +// SecondsStyle.DOTS.id -> { +// drawDots(canvas, bounds, zonedDateTime) // } -// -//// drawTimeOld(canvas, bounds, hour, hourPaint, -77f, -7f, scaleOffset) // Rect(0, 0, 152, 14)) -//// drawTimeOld( -//// canvas, -//// bounds, -//// zonedDateTime.minute, -//// minutePaint, -//// -77f, -//// 105f, -//// scaleOffset -//// )//Rect(0, 0, 152, -210)) // } } @@ -918,19 +901,10 @@ class WatchCanvasRenderer( drawComplications(canvas, zonedDateTime) // } -// if (renderParameters.watchFaceLayesr.contains(WatchFaceLayer.COMPLICATIONS) && -// drawProperties.timeScale != 0f -// ) { -// drawComplications(canvas, zonedDateTime) -// } } override fun shouldAnimate(): Boolean { - return ambientEnterAnimator.isRunning || super.shouldAnimate() - } - - private fun animating(): Boolean { - return ambientEnterAnimator.isRunning || ambientExitAnimator.isRunning + return super.shouldAnimate() || ambientEnterAnimator.isRunning || ambientExitAnimator.isRunning } // ----- All drawing functions ----- @@ -974,228 +948,19 @@ class WatchCanvasRenderer( } } - private fun drawSeconds( + private fun drawText( canvas: Canvas, bounds: Rect, - time: Int, - paint: Paint, - offsetX: Float, - offsetY: Float, - textSize: Float, - textScale: Float, - ) { - val text = time.toString().padStart(2, '0') - - var opacity = - if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( - drawProperties.timeScale - ) - opacity = 1f - - val bitmap = renderSeconds(text, paint, textSize, watchFaceColors.tertiaryColor, opacity) - - canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawBitmap( - bitmap, - 192f - bitmap.width / 2 + offsetX, - 192f - bitmap.height / 2 + offsetY, - Paint(), - ) - } - } - - private fun renderSeconds( text: String, paint: Paint, - textSize: Float, - color: Int, opacity: Float, - ): Bitmap { - val hash = "${text},${textSize},${color},${opacity}" - - val cached = bitmapCache.get(SECONDS_BITMAP_KEY, hash) - if (cached != null) { - return cached - } - - val p = Paint(paint) - p.textSize *= textSize - p.color = ColorUtils.blendARGB(Color.TRANSPARENT, color, opacity) - - val textBounds = Rect() - p.getTextBounds(text, 0, text.length, textBounds) - val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) - - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - val canvas = Canvas(bitmap) - - canvas.drawText( - text, - 0f, - bounds.height().toFloat(), - p, - ) - - // DEBUG -------------------- - canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#22ffffff") - }) - val p2 = Paint() - p2.color = Color.WHITE - canvas.drawText( - "r ${bitmapCache.loads(SECONDS_BITMAP_KEY)} w ${bitmapCache.renders(SECONDS_BITMAP_KEY)}", - 0f, - bounds.height().toFloat(), - p2, - ) - // --------------------------- - - bitmapCache.set(SECONDS_BITMAP_KEY, hash, bitmap) - - return bitmap - } - - private fun drawAmPm( - canvas: Canvas, - bounds: Rect, - ampm: String, - paint: Paint, - offsetX: Float, - offsetY: Float, - textScale: Float, - ) { - val text = ampm.uppercase() - - var opacity = - if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( - drawProperties.timeScale - ) - opacity = 1f - - val bitmap = renderAmPm(text, paint, 5f, AMPM_BITMAP_KEY) - - canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawBitmap( - bitmap, - 192f - bitmap.width / 2 + offsetX, - 192f - bitmap.height / 2 + offsetY, - Paint(), - ) - } - - - - - - - -// val p = Paint(paint) -//// p.textSize *= 5f -//// p.isAntiAlias = true -//// p.isDither = true -//// p.isFilterBitmap = true -// -// var scale = _scale / 14f -//// scale = 1f -// -// val cacheBitmap = Bitmap.createBitmap( -// bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 -// ) -// val bitmapCanvas = Canvas(cacheBitmap) -// -// val textBounds = Rect() -// p.getTextBounds(text, 0, text.length, textBounds) -// p.color = ColorUtils.blendARGB(Color.TRANSPARENT, watchFaceColors.tertiaryColor, opacity) -// -// bitmapCanvas.drawText( -// text, -// 192f, -// 192f + textBounds.height() / 2, -// p, -// ) -// -// canvas.withScale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) { -// canvas.withTranslation(offsetX, offsetY) { -// canvas.drawBitmap( -// cacheBitmap, -// 0f, -// 0f, -// Paint(), -// ) -// } -// } - } - - private fun renderAmPm( - text: String, - paint: Paint, - textSize: Float, - cacheKey: String, - ): Bitmap { - val hash = "${text},${textSize}" - - val cached = bitmapCache.get(cacheKey, hash) - if (cached != null) { - return cached - } - - val p = Paint(paint) - p.textSize *= textSize - - val textBounds = Rect() - p.getTextBounds(text, 0, text.length, textBounds) - val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) - - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - val canvas = Canvas(bitmap) - - canvas.drawText( - text, - 0f, - bounds.height().toFloat(), - p, - ) - - // DEBUG -------------------- - canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#22ffffff") - }) - val p2 = Paint() - p2.color = Color.WHITE - canvas.drawText( - "r ${bitmapCache.loads(cacheKey)} w ${bitmapCache.renders(cacheKey)}", - 0f, - bounds.height().toFloat(), - p2, - ) - // --------------------------- - - bitmapCache.set(cacheKey, hash, bitmap) - - return bitmap - } - - private fun drawTime( - canvas: Canvas, - bounds: Rect, - time: Int, - paint: Paint, offsetX: Float, offsetY: Float, textSize: Float, textScale: Float, cacheKey: String, ) { - val text = time.toString().padStart(2, '0') - - val p = Paint(paint) - p.textSize *= 14f - - val bitmap = renderTime(text, paint, textSize, cacheKey) + val bitmap = renderText(text, paint, textSize, paint.color, opacity, cacheKey) canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { canvas.drawBitmap( @@ -1207,13 +972,15 @@ class WatchCanvasRenderer( } } - private fun renderTime( + private fun renderText( text: String, paint: Paint, textSize: Float, + color: Int, + opacity: Float, cacheKey: String, ): Bitmap { - val hash = "${text},${textSize}" + val hash = "${text},${textSize},${color},${opacity}" val cached = bitmapCache.get(cacheKey, hash) if (cached != null) { @@ -1222,6 +989,7 @@ class WatchCanvasRenderer( val p = Paint(paint) p.textSize *= textSize + p.color = ColorUtils.blendARGB(Color.TRANSPARENT, color, opacity) val textBounds = Rect() p.getTextBounds(text, 0, text.length, textBounds) @@ -1239,19 +1007,19 @@ class WatchCanvasRenderer( p, ) - // DEBUG -------------------- - canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#22ffffff") - }) - val p2 = Paint() - p2.color = Color.WHITE - canvas.drawText( - "r ${bitmapCache.loads(cacheKey)} w ${bitmapCache.renders(cacheKey)}", - 0f, - bounds.height().toFloat(), - p2, - ) - // --------------------------- +// // DEBUG -------------------- +// canvas.drawRect(bounds, Paint().apply { +// this.color = Color.parseColor("#22ffffff") +// }) +// val p2 = Paint() +// p2.color = Color.WHITE +// canvas.drawText( +// "r ${bitmapCache.loads(cacheKey)} w ${bitmapCache.renders(cacheKey)}", +// 0f, +// bounds.height().toFloat(), +// p2, +// ) +// // --------------------------- bitmapCache.set(cacheKey, hash, bitmap) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index a8b14b8..d1b2de2 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -1428,16 +1428,10 @@ fun ComplicationButton( // shape = RoundedCornerShape(16.dp), modifier = Modifier .weight(bottom - top, true) +// .background(Color.Blue) .fillMaxSize(), ) {} -// Box( -// Modifier -// .fillMaxWidth() -// .weight(bottom - top, true) -// .alpha(.3f) -// .background(Color.Blue) -// ) Box(Modifier.weight(1f - bottom, true)) } Box(Modifier.weight(1f - right, true)) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index 8343280..888fc6b 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -133,9 +133,9 @@ const val RIGHT_ICON_COMPLICATION_RIGHT_BOUND = 306f / 384f + 54f / 384f const val RIGHT_ICON_COMPLICATION_BOTTOM_BOUND = 126f / 384f + 132f / 384f const val RIGHT_TEXT_COMPLICATION_LEFT_BOUND = 249f / 384f - 14f / 384f -const val RIGHT_TEXT_COMPLICATION_TOP_BOUND = 246f / 384f - 14f / 384f +const val RIGHT_TEXT_COMPLICATION_TOP_BOUND = 246f / 384f - 14f / 384f + 2f / 384f const val RIGHT_TEXT_COMPLICATION_RIGHT_BOUND = 249f / 384f + 82f / 384f + 14f / 384f -const val RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND = 246f / 384f + 14f / 384f + 14f / 384f +const val RIGHT_TEXT_COMPLICATION_BOTTOM_BOUND = 246f / 384f + 14f / 384f + 14f / 384f + 2f / 384f const val HOUR_COMPLICATION_LEFT_BOUND = 114f / 384f const val HOUR_COMPLICATION_TOP_BOUND = 87f / 384f From 9cd81998f127711f129b2609241909988359e1bb Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Fri, 16 Feb 2024 00:18:57 +0200 Subject: [PATCH 23/52] Toggleable debug overlay for render elements and complications (wip) --- .../main/java/dev/rdnt/m8face/BitmapCache.kt | 8 +- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 104 +++++++++++++----- .../m8face/data/watchface/WatchFaceData.kt | 1 + .../m8face/editor/WatchFaceConfigActivity.kt | 1 + .../editor/WatchFaceConfigStateHolder.kt | 17 +++ .../m8face/utils/HorizontalComplication.kt | 26 ++++- .../utils/HorizontalTextComplication.kt | 26 ++++- .../dev/rdnt/m8face/utils/IconComplication.kt | 32 +++++- .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 12 ++ .../rdnt/m8face/utils/VerticalComplication.kt | 33 +++++- app/src/main/res/values/strings.xml | 2 + 11 files changed, 215 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt index 5f069f3..e77508f 100644 --- a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt +++ b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt @@ -20,17 +20,17 @@ class BitmapCache { } fun renders(k: String): Int { - return entries[k]?.renders ?: 0 + return entries[k]?.renders ?: 1 } fun loads(k: String): Int { - return entries[k]?.loads ?: 0 + return entries[k]?.loads ?: 1 } } class BitmapCacheEntry { - var renders: Int = 0 - var loads: Int = 0 + var renders: Int = 1 + var loads: Int = 1 private var hash: String = "" private var bitmap: Bitmap? = null diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 3037ed2..6c7a0b9 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -50,6 +50,7 @@ import dev.rdnt.m8face.data.watchface.WatchFaceData import dev.rdnt.m8face.utils.AMBIENT_STYLE_SETTING import dev.rdnt.m8face.utils.BIG_AMBIENT_SETTING import dev.rdnt.m8face.utils.COLOR_STYLE_SETTING +import dev.rdnt.m8face.utils.DEBUG_SETTING import dev.rdnt.m8face.utils.DETAILED_AMBIENT_SETTING import dev.rdnt.m8face.utils.HorizontalComplication import dev.rdnt.m8face.utils.HorizontalTextComplication @@ -114,6 +115,7 @@ class WatchCanvasRenderer( militaryTime = true, bigAmbient = false, layoutStyle = LayoutStyle.INFO1, + debug = false, ) // Converts resource ids into Colors and ComplicationDrawable. @@ -244,6 +246,8 @@ class WatchCanvasRenderer( private var is24Format: Boolean = watchFaceData.militaryTime + private var debug: Boolean = watchFaceData.debug + private val ambientTransitionMs = 1000L private var drawProperties = DrawProperties() private var isHeadless = false @@ -451,6 +455,14 @@ class WatchCanvasRenderer( detailedAmbient = booleanValue.value, ) } + + DEBUG_SETTING -> { + val booleanValue = options.value as UserStyleSetting.BooleanUserStyleSetting.BooleanOption + + newWatchFaceData = newWatchFaceData.copy( + debug = booleanValue.value, + ) + } } } @@ -524,6 +536,7 @@ class WatchCanvasRenderer( } is24Format = watchFaceData.militaryTime + debug = watchFaceData.debug interactiveFrameDelay = when (watchFaceData.secondsStyle.id) { SecondsStyle.NONE.id -> if (shouldDrawSeconds) 1000 else 60000 @@ -793,7 +806,7 @@ class WatchCanvasRenderer( } } - private var renders: Int = 0 + private var frame: Int = 0 override fun render( canvas: Canvas, @@ -801,10 +814,29 @@ class WatchCanvasRenderer( zonedDateTime: ZonedDateTime, sharedAssets: AnalogSharedAssets, ) { - renders++ + frame++ canvas.drawColor(Color.parseColor("#ff000000")) + if (debug) { + val p2 = Paint() + p2.color = Color.parseColor("#aaff1111") + p2.typeface = context.resources.getFont(R.font.m8stealth57) + p2.textSize = 8f + + val text = "f $frame" + + val textBounds = Rect() + p2.getTextBounds(text, 0, text.length, textBounds) + + canvas.drawText( + text, + 192f - textBounds.width() / 2, + 192f + textBounds.height() / 2, + p2, + ) + } + // val p2 = Paint() // p2.color = Color.WHITE // canvas.drawText( @@ -883,15 +915,15 @@ class WatchCanvasRenderer( ) } -// when (watchFaceData.secondsStyle.id) { -// SecondsStyle.DASHES.id -> { -// drawDashes(canvas, bounds, zonedDateTime) -// } -// -// SecondsStyle.DOTS.id -> { -// drawDots(canvas, bounds, zonedDateTime) -// } -// } + when (watchFaceData.secondsStyle.id) { + SecondsStyle.DASHES.id -> { + drawDashes(canvas, bounds, zonedDateTime) + } + + SecondsStyle.DOTS.id -> { + drawDots(canvas, bounds, zonedDateTime) + } + } } @@ -913,7 +945,8 @@ class WatchCanvasRenderer( if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( drawProperties.timeScale ) - opacity = 1f +// opacity = 1f + val offsetX = if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id) interpolate(34f, 0f) else 0f val scale = interpolate(16f / 14f, 1f) @@ -924,21 +957,27 @@ class WatchCanvasRenderer( is VerticalComplication -> { (complication.renderer as VerticalComplication).opacity = opacity (complication.renderer as VerticalComplication).scale = scale + (complication.renderer as VerticalComplication).debug = debug } is HorizontalComplication -> { (complication.renderer as HorizontalComplication).opacity = opacity (complication.renderer as HorizontalComplication).offsetX = offsetX (complication.renderer as HorizontalComplication).scale = scale + (complication.renderer as HorizontalComplication).debug = debug } is HorizontalTextComplication -> { (complication.renderer as HorizontalTextComplication).opacity = opacity (complication.renderer as HorizontalTextComplication).offsetX = offsetX (complication.renderer as HorizontalTextComplication).scale = scale + (complication.renderer as HorizontalTextComplication).debug = debug } - is IconComplication -> (complication.renderer as IconComplication).opacity = opacity + is IconComplication -> { + (complication.renderer as IconComplication).opacity = opacity + (complication.renderer as IconComplication).debug = debug + } else -> {} } @@ -980,7 +1019,7 @@ class WatchCanvasRenderer( opacity: Float, cacheKey: String, ): Bitmap { - val hash = "${text},${textSize},${color},${opacity}" + val hash = "${text},${textSize},${color},${opacity},${debug}" val cached = bitmapCache.get(cacheKey, hash) if (cached != null) { @@ -1007,19 +1046,30 @@ class WatchCanvasRenderer( p, ) -// // DEBUG -------------------- -// canvas.drawRect(bounds, Paint().apply { -// this.color = Color.parseColor("#22ffffff") -// }) -// val p2 = Paint() -// p2.color = Color.WHITE -// canvas.drawText( -// "r ${bitmapCache.loads(cacheKey)} w ${bitmapCache.renders(cacheKey)}", -// 0f, -// bounds.height().toFloat(), -// p2, -// ) -// // --------------------------- + if (debug) { + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#aaf2e900") + style = Paint.Style.STROKE + strokeWidth = 4f + }) + val p2 = Paint() + p2.color = Color.parseColor("#aaf2e900") + p2.typeface = context.resources.getFont(R.font.m8stealth57) + p2.textSize = 8f + canvas.drawText( + "r ${bitmapCache.loads(cacheKey)}", + 4f, + bounds.height().toFloat() - 13f, + p2, + ) + + canvas.drawText( + "w ${bitmapCache.renders(cacheKey)}", + 4f, + bounds.height().toFloat() - 4f, + p2, + ) + } bitmapCache.set(cacheKey, hash, bitmap) diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt index 7dd0133..559e353 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt @@ -26,4 +26,5 @@ data class WatchFaceData( val militaryTime: Boolean = true, val bigAmbient: Boolean = true, val detailedAmbient: Boolean = false, + val debug: Boolean = false, ) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index d1b2de2..ab0d05f 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -349,6 +349,7 @@ fun WatchfaceConfigApp( val militaryTimeEnabled = state.userStylesAndPreview.militaryTime val bigAmbientEnabled = state.userStylesAndPreview.bigAmbient val detailedAmbientEnabled = state.userStylesAndPreview.detailedAmbient +// val debugEnabled = state.userStylesAndPreview.debug Box( Modifier diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index 47140f2..b5f747e 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -67,6 +67,7 @@ class WatchFaceConfigStateHolder( private lateinit var militaryTimeKey: UserStyleSetting.BooleanUserStyleSetting private lateinit var bigAmbientKey: UserStyleSetting.BooleanUserStyleSetting private lateinit var detailedAmbientKey: UserStyleSetting.BooleanUserStyleSetting + private lateinit var debugKey: UserStyleSetting.BooleanUserStyleSetting val uiState: StateFlow = flow { @@ -128,6 +129,10 @@ class WatchFaceConfigStateHolder( DETAILED_AMBIENT_SETTING -> { detailedAmbientKey = setting as UserStyleSetting.BooleanUserStyleSetting } + + DEBUG_SETTING -> { + debugKey = setting as UserStyleSetting.BooleanUserStyleSetting + } } } } @@ -176,6 +181,9 @@ class WatchFaceConfigStateHolder( val detailedAmbient = userStyle[detailedAmbientKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption + val debug = + userStyle[debugKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption + return UserStylesAndPreview( layoutStyleId = layoutStyle.id.toString(), colorStyleId = colorStyle.id.toString(), @@ -184,6 +192,7 @@ class WatchFaceConfigStateHolder( militaryTime = militaryTime.value, bigAmbient = bigAmbient.value, detailedAmbient = detailedAmbient.value, + debug = debug.value, previewImage = bitmap, ) } @@ -382,6 +391,13 @@ class WatchFaceConfigStateHolder( } } + fun setDebug(enabled: Boolean) { + setUserStyleOption( + debugKey, + UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(enabled) + ) + } + // Saves User Style Option change back to the back to the EditorSession. // Note: The UI widgets in the Activity that can trigger this method (through the 'set' methods) // will only be enabled after the EditorSession has been initialized. @@ -415,6 +431,7 @@ class WatchFaceConfigStateHolder( val militaryTime: Boolean, val bigAmbient: Boolean, val detailedAmbient: Boolean, + val debug: Boolean, val previewImage: Bitmap, ) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index 2d9087b..85fef6d 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -21,11 +21,16 @@ import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.NoDataComplicationData import androidx.wear.watchface.complications.data.ShortTextComplicationData +import dev.rdnt.m8face.BitmapCacheEntry import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime class HorizontalComplication(private val context: Context) : CanvasComplication { + private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() + + var debug: Boolean = false; + var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { field = tertiaryColor @@ -90,10 +95,23 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) { if (bounds.isEmpty) return -// // DEBUG -// canvas.drawRect(bounds, Paint().apply { -// color = Color.parseColor("#22ffffff") -// }) + if (debug) { + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#aa02d7f2") + style = Paint.Style.STROKE + strokeWidth = 2f + }) + val p2 = Paint() + p2.color = Color.parseColor("#aa02d7f2") + p2.typeface = context.resources.getFont(R.font.m8stealth57) + p2.textSize = 8f + canvas.drawText( + "r ${bitmapCache.loads} w ${bitmapCache.renders}", + bounds.left + 3f, + bounds.bottom - 3f, + p2, + ) + } when (data.type) { ComplicationType.SHORT_TEXT -> { diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt index bbd8646..dc38890 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt @@ -22,11 +22,16 @@ import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.NoDataComplicationData import androidx.wear.watchface.complications.data.ShortTextComplicationData +import dev.rdnt.m8face.BitmapCacheEntry import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime class HorizontalTextComplication(private val context: Context) : CanvasComplication { + private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() + + var debug: Boolean = false; + var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { field = tertiaryColor @@ -63,10 +68,23 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat ) { if (bounds.isEmpty) return -// // DEBUG -// canvas.drawRect(bounds, Paint().apply { -// color = Color.parseColor("#22ffffff") -// }) + if (debug) { + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#aa02d7f2") + style = Paint.Style.STROKE + strokeWidth = 2f + }) + val p2 = Paint() + p2.color = Color.parseColor("#aa02d7f2") + p2.typeface = context.resources.getFont(R.font.m8stealth57) + p2.textSize = 8f + canvas.drawText( + "r ${bitmapCache.loads} w ${bitmapCache.renders}", + bounds.left + 3f, + bounds.bottom - 3f, + p2, + ) + } when (data.type) { ComplicationType.SHORT_TEXT -> { diff --git a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt index 6c317df..446d52e 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt @@ -12,11 +12,16 @@ import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.complications.data.* +import dev.rdnt.m8face.BitmapCacheEntry import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime class IconComplication(private val context: Context) : CanvasComplication { + private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() + + var debug: Boolean = false; + var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { field = tertiaryColor @@ -48,10 +53,29 @@ class IconComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return -// // DEBUG -// canvas.drawRect(bounds, Paint().apply { -// color = Color.parseColor("#22ffffff") -// }) + if (debug) { + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#aa02d7f2") + style = Paint.Style.STROKE + strokeWidth = 2f + }) + val p2 = Paint() + p2.color = Color.parseColor("#aa02d7f2") + p2.typeface = context.resources.getFont(R.font.m8stealth57) + p2.textSize = 8f + canvas.drawText( + "r ${bitmapCache.loads}", + bounds.left + 3f, + bounds.bottom - 12f, + p2, + ) + canvas.drawText( + "w ${bitmapCache.renders}", + bounds.left + 3f, + bounds.bottom - 3f, + p2, + ) + } when (data.type) { ComplicationType.MONOCHROMATIC_IMAGE -> { diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index 4106979..fcbdc7d 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -45,6 +45,7 @@ const val SECONDS_STYLE_SETTING = "seconds_style_setting" const val MILITARY_TIME_SETTING = "military_time_setting" const val BIG_AMBIENT_SETTING = "big_ambient_setting" const val DETAILED_AMBIENT_SETTING = "detailed_ambient_setting" +const val DEBUG_SETTING = "debug_setting" /* * Creates user styles in the settings activity associated with the watch face, so users can @@ -354,6 +355,16 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { false, ) + val debugSetting = UserStyleSetting.BooleanUserStyleSetting( + UserStyleSetting.Id(DEBUG_SETTING), + context.resources, + R.string.debug_setting, + R.string.debug_setting_description, + null, + listOf(WatchFaceLayer.BASE), + DateFormat.is24HourFormat(context), // default + ) + // 4. Create style settings to hold all options. return UserStyleSchema( listOf( @@ -364,6 +375,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { militaryTimeSetting, bigAmbientSetting, detailedAmbientSetting, + debugSetting, ) ) } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index cad06e3..3a2fe44 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -12,11 +12,17 @@ import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.complications.data.* +import dev.rdnt.m8face.BitmapCache +import dev.rdnt.m8face.BitmapCacheEntry import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime class VerticalComplication(private val context: Context) : CanvasComplication { + private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() + + var debug: Boolean = false; + var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { field = tertiaryColor @@ -83,10 +89,29 @@ class VerticalComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return -// // DEBUG -// canvas.drawRect(bounds, Paint().apply { -// color = Color.parseColor("#22ffffff") -// }) + if (debug) { + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#aa02d7f2") + style = Paint.Style.STROKE + strokeWidth = 2f + }) + val p2 = Paint() + p2.color = Color.parseColor("#aa02d7f2") + p2.typeface = context.resources.getFont(R.font.m8stealth57) + p2.textSize = 8f + canvas.drawText( + "r ${bitmapCache.loads}", + bounds.left + 3f, + bounds.bottom - 12f, + p2, + ) + canvas.drawText( + "w ${bitmapCache.renders}", + bounds.left + 3f, + bounds.bottom - 3f, + p2, + ) + } when (data.type) { ComplicationType.SHORT_TEXT -> { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4b8a1d9..d0c5e10 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -146,6 +146,7 @@ Military Time Big Ambient Detailed Ambient + Debug @@ -153,6 +154,7 @@ Whether to use military instead of standard time Use big time on ambient mode Show complications on ambient mode + >.> From 633c51afa68506b5940f1915bd77986a11c6504b Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Fri, 16 Feb 2024 01:21:24 +0200 Subject: [PATCH 24/52] Bitmap cache on most complications --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 10 +- .../m8face/utils/HorizontalComplication.kt | 109 ++++---- .../utils/HorizontalTextComplication.kt | 118 +++++---- .../rdnt/m8face/utils/VerticalComplication.kt | 236 ++++++++++-------- 4 files changed, 260 insertions(+), 213 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 6c7a0b9..208e691 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -1050,7 +1050,7 @@ class WatchCanvasRenderer( canvas.drawRect(bounds, Paint().apply { this.color = Color.parseColor("#aaf2e900") style = Paint.Style.STROKE - strokeWidth = 4f + strokeWidth = 2f }) val p2 = Paint() p2.color = Color.parseColor("#aaf2e900") @@ -1058,15 +1058,15 @@ class WatchCanvasRenderer( p2.textSize = 8f canvas.drawText( "r ${bitmapCache.loads(cacheKey)}", - 4f, - bounds.height().toFloat() - 13f, + 3f, + bounds.height().toFloat() - 12f, p2, ) canvas.drawText( "w ${bitmapCache.renders(cacheKey)}", - 4f, - bounds.height().toFloat() - 4f, + 3f, + bounds.height().toFloat() - 3f, p2, ) } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index 85fef6d..7f75ceb 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -13,7 +13,6 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.withScale -import androidx.core.graphics.withTranslation import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters @@ -95,6 +94,55 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) { if (bounds.isEmpty) return + val bitmap: Bitmap + + when (data.type) { + ComplicationType.SHORT_TEXT -> { + bitmap = drawShortTextComplication(bounds, data as ShortTextComplicationData) + } + + else -> return + } + + canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + canvas.drawBitmap( + bitmap, + bounds.left + offsetX, + bounds.top.toFloat(), + Paint(), + ) + } + } + + + private fun drawShortTextComplication( + bounds: Rect, + data: ShortTextComplicationData + ): Bitmap { + val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${opacity},${debug}" + + val cached = bitmapCache.get(hash) + if (cached != null) { + return cached + } + + val bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(bitmap) + + val rect = Rect(0, 0, bitmap.width, bitmap.height) + + renderShortTextComplication(bitmapCanvas, rect, data) + + renderDebug(bitmapCanvas, rect) + + bitmapCache.set(hash, bitmap) + + return bitmap + } + + private fun renderDebug(canvas: Canvas, bounds: Rect) { if (debug) { canvas.drawRect(bounds, Paint().apply { this.color = Color.parseColor("#aa02d7f2") @@ -112,20 +160,12 @@ class HorizontalComplication(private val context: Context) : CanvasComplication p2, ) } - - when (data.type) { - ComplicationType.SHORT_TEXT -> { - renderShortTextComplication(canvas, bounds, data as ShortTextComplicationData) - } - - else -> return - } } private fun renderShortTextComplication( canvas: Canvas, bounds: Rect, - data: ShortTextComplicationData, + data: ShortTextComplicationData ) { val now = Instant.now() @@ -146,11 +186,11 @@ class HorizontalComplication(private val context: Context) : CanvasComplication if (isBattery) { val drawable = ContextCompat.getDrawable(context, R.drawable.battery_icon_32)!! icon = drawable.toBitmap( - (32f / 384 * canvas.width).toInt(), - (32f / 384 * canvas.width).toInt() + (32f / 186f * canvas.width).toInt(), + (32f / 186f * canvas.width).toInt() ) iconBounds = - Rect(0, 0, (32f / 384 * canvas.width).toInt(), (32f / 384 * canvas.width).toInt()) + Rect(0, 0, (32f / 186f * canvas.width).toInt(), (32f / 186f * canvas.width).toInt()) } else if (data.monochromaticImage != null) { val drawable = data.monochromaticImage!!.image.loadDrawable(context) if (drawable != null) { @@ -172,7 +212,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication title = data.title!!.getTextAt(context.resources, now).toString().uppercase() } - textPaint.textSize = 24F / 384 * canvas.width + textPaint.textSize = 24F / 186f * canvas.width val textBounds = Rect() @@ -199,37 +239,24 @@ class HorizontalComplication(private val context: Context) : CanvasComplication titleOffsetX = (width - titleBounds.width()).toFloat() / 2f textOffsetX = (width - textBounds.width()).toFloat() / 2f - titleOffsetX += 6f / 384f * canvas.width - textOffsetX += 6f / 384f * canvas.width + titleOffsetX += 6f / 186f * canvas.width + textOffsetX += 6f / 186f * canvas.width } else if (icon != null) { val width = iconBounds.width() + textBounds.width() iconOffsetX = (width - iconBounds.width()).toFloat() / 2f textOffsetX = (width - textBounds.width()).toFloat() / 2f - iconOffsetX += 9f / 384f * canvas.width - textOffsetX += 9f / 384f * canvas.width + iconOffsetX += 9f / 186f * canvas.width + textOffsetX += 9f / 186f * canvas.width if (isBattery) { iconOffsetX = iconOffsetX.toInt().toFloat() } } -// textOffsetX += offsetX -// titleOffsetX += offsetX -// iconOffsetX -= offsetX - - - val cacheBitmap = Bitmap.createBitmap( - canvas.width, - canvas.height, - Bitmap.Config.ARGB_8888 - ) - val bitmapCanvas = Canvas(cacheBitmap) - - if (title != null) { - bitmapCanvas.drawText( + canvas.drawText( title, bounds.exactCenterX() - titleBounds.width() / 2 - titleOffsetX, bounds.exactCenterY() + titleBounds.height() / 2, @@ -243,7 +270,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication bounds.exactCenterY() + iconBounds.height() / 2f, ) - bitmapCanvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) + canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) } @@ -251,7 +278,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication val prefix = "".padStart(prefixLen, '0') prefixPaint.textSize = textPaint.textSize - bitmapCanvas.drawText( + canvas.drawText( prefix, bounds.exactCenterX() - textBounds.width() / 2 + textOffsetX, bounds.exactCenterY() + textBounds.height() / 2, @@ -259,24 +286,12 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) } - bitmapCanvas.drawText( + canvas.drawText( text, bounds.exactCenterX() - textBounds.width() / 2 + textOffsetX, bounds.exactCenterY() + textBounds.height() / 2, textPaint ) - - - canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { - canvas.withTranslation(offsetX, 0f) { - canvas.drawBitmap( - cacheBitmap, - 0f, - 0f, - Paint(), - ) - } - } } override fun drawHighlight( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt index dc38890..6cdc0ca 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt @@ -68,31 +68,52 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat ) { if (bounds.isEmpty) return - if (debug) { - canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#aa02d7f2") - style = Paint.Style.STROKE - strokeWidth = 2f - }) - val p2 = Paint() - p2.color = Color.parseColor("#aa02d7f2") - p2.typeface = context.resources.getFont(R.font.m8stealth57) - p2.textSize = 8f - canvas.drawText( - "r ${bitmapCache.loads} w ${bitmapCache.renders}", - bounds.left + 3f, - bounds.bottom - 3f, - p2, - ) - } + + val bitmap: Bitmap when (data.type) { ComplicationType.SHORT_TEXT -> { - renderShortTextComplication(canvas, bounds, data as ShortTextComplicationData) + bitmap = drawShortTextComplication(bounds, data as ShortTextComplicationData) } else -> return } + + canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + canvas.drawBitmap( + bitmap, + bounds.left + offsetX, + bounds.top.toFloat(), + Paint(), + ) + } + } + + private fun drawShortTextComplication( + bounds: Rect, + data: ShortTextComplicationData + ): Bitmap { + val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${opacity},${debug}" + + val cached = bitmapCache.get(hash) + if (cached != null) { + return cached + } + + val bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(bitmap) + + val rect = Rect(0, 0, bitmap.width, bitmap.height) + + renderShortTextComplication(bitmapCanvas, rect, data) + + renderDebug(bitmapCanvas, rect) + + bitmapCache.set(hash, bitmap) + + return bitmap } private fun renderShortTextComplication( @@ -108,7 +129,7 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat } val p = Paint(textPaint) - p.textSize *= 14f / 42f * bounds.height() + p.textSize *= 14f // val textBounds = Rect() // p.getTextBounds(text, 0, text.length, textBounds) @@ -116,51 +137,38 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat - val cacheBitmap = Bitmap.createBitmap( - bounds.width(), - bounds.height(), - Bitmap.Config.ARGB_8888 - ) - val bitmapCanvas = Canvas(cacheBitmap) - val textBounds = Rect() p.getTextBounds(text, 0, text.length, textBounds) - bitmapCanvas.drawText( + canvas.drawText( text, // 192f-textBounds.width().toFloat()/2f, // 192f+textBounds.height()/2+offsetY, - 0f, - bounds.height().toFloat()/2f, -// bounds.left.toFloat() + 14f / 42f * bounds.height(), -// bounds.exactCenterY() + textBounds.height() / 2, +// 0f, +// bounds.height().toFloat()/2f, + bounds.left.toFloat() + 14f, + bounds.exactCenterY() + textBounds.height() / 2, p, ) + } - - - - - canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { - canvas.withTranslation(offsetX, 0f) { - canvas.drawBitmap( - cacheBitmap, - bounds.left.toFloat() + 14f / 42f * bounds.height(), -// bounds.top.toFloat(), - bounds.top.toFloat() + textBounds.height() / 2, -// bounds.left.toFloat() + 14f / 42f * bounds.height(), -// bounds.exactCenterY() + textBounds.height() / 2, - - Paint(), - ) - -// canvas.drawText( -// text, -// bounds.left.toFloat() + 14f / 42f * bounds.height(), -// bounds.exactCenterY() + textBounds.height() / 2, -// p -// ) - } + private fun renderDebug(canvas: Canvas, bounds: Rect) { + if (debug) { + canvas.drawRect(bounds, Paint().apply { + this.color = Color.parseColor("#aa02d7f2") + style = Paint.Style.STROKE + strokeWidth = 2f + }) + val p2 = Paint() + p2.color = Color.parseColor("#aa02d7f2") + p2.typeface = context.resources.getFont(R.font.m8stealth57) + p2.textSize = 8f + canvas.drawText( + "r ${bitmapCache.loads} w ${bitmapCache.renders}", + bounds.left + 3f, + bounds.bottom - 3f, + p2, + ) } } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index 3a2fe44..b2064c9 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -89,6 +89,119 @@ class VerticalComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return + val bitmap: Bitmap + + when (data.type) { + ComplicationType.SHORT_TEXT -> { + bitmap = drawShortTextComplication(bounds, data as ShortTextComplicationData) + } + + ComplicationType.MONOCHROMATIC_IMAGE -> { + bitmap = drawMonochromaticImageComplication( + bounds, + data as MonochromaticImageComplicationData + ) + } + + ComplicationType.SMALL_IMAGE -> { + bitmap = drawSmallImageComplication(bounds, data as SmallImageComplicationData) + } + + else -> return + } + + canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + canvas.drawBitmap( + bitmap, + bounds.left.toFloat(), + bounds.top.toFloat(), + Paint(), + ) + } + } + + private fun drawShortTextComplication( + bounds: Rect, + data: ShortTextComplicationData + ): Bitmap { + val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${opacity},${debug}" + + val cached = bitmapCache.get(hash) + if (cached != null) { + return cached + } + + val bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(bitmap) + + val rect = Rect(0, 0, bitmap.width, bitmap.height) + + renderShortTextComplication(bitmapCanvas, rect, data) + + renderDebug(bitmapCanvas, rect) + + bitmapCache.set(hash, bitmap) + + return bitmap + } + + private fun drawMonochromaticImageComplication( + bounds: Rect, + data: MonochromaticImageComplicationData + ): Bitmap { + val hash = "${bounds},${data.monochromaticImage.image.resId},${tertiaryColor},${opacity},${debug}" + + val cached = bitmapCache.get(hash) + if (cached != null) { + return cached + } + + val bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(bitmap) + + val rect = Rect(0, 0, bitmap.width, bitmap.height) + + renderMonochromaticImageComplication(bitmapCanvas, rect, data) + + renderDebug(bitmapCanvas, rect) + + bitmapCache.set(hash, bitmap) + + return bitmap + } + + private fun drawSmallImageComplication( + bounds: Rect, + data: SmallImageComplicationData + ): Bitmap { + val hash = "${bounds},${data.smallImage.image.resId},${tertiaryColor},${opacity},${debug}" + + val cached = bitmapCache.get(hash) + if (cached != null) { + return cached + } + + val bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + val bitmapCanvas = Canvas(bitmap) + + val rect = Rect(0, 0, bitmap.width, bitmap.height) + + renderSmallImageComplication(bitmapCanvas, rect, data) + + renderDebug(bitmapCanvas, rect) + + bitmapCache.set(hash, bitmap) + + return bitmap + } + + private fun renderDebug(canvas: Canvas, bounds: Rect) { if (debug) { canvas.drawRect(bounds, Paint().apply { this.color = Color.parseColor("#aa02d7f2") @@ -112,26 +225,6 @@ class VerticalComplication(private val context: Context) : CanvasComplication { p2, ) } - - when (data.type) { - ComplicationType.SHORT_TEXT -> { - renderShortTextComplication(canvas, bounds, data as ShortTextComplicationData) - } - - ComplicationType.MONOCHROMATIC_IMAGE -> { - renderMonochromaticImageComplication( - canvas, - bounds, - data as MonochromaticImageComplicationData - ) - } - - ComplicationType.SMALL_IMAGE -> { - renderSmallImageComplication(canvas, bounds, data as SmallImageComplicationData) - } - - else -> return - } } private fun renderShortTextComplication( @@ -158,11 +251,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { if (isBattery) { val drawable = ContextCompat.getDrawable(context, R.drawable.battery_icon_32)!! icon = drawable.toBitmap( - (32f / 384f * canvas.width).toInt(), - (32f / 384f * canvas.width).toInt() + (32f).toInt(), + (32f).toInt() ) iconBounds = - Rect(0, 0, (32f / 384f * canvas.width).toInt(), (32f / 384f * canvas.width).toInt()) + Rect(0, 0, (32f).toInt(), (32f).toInt()) } else if (data.monochromaticImage != null) { val drawable = data.monochromaticImage!!.image.loadDrawable(context) if (drawable != null) { @@ -185,11 +278,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { } if (text.length <= 3) { - textPaint.textSize = 24F / 384f * canvas.width + textPaint.textSize = 24F } else if (text.length <= 6) { - textPaint.textSize = 16F / 384f * canvas.width + textPaint.textSize = 16F } else { - textPaint.textSize = 12F / 384f * canvas.width + textPaint.textSize = 12F } val textBounds = Rect() @@ -204,11 +297,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { if (title != null) { if (title.length <= 3) { - titlePaint.textSize = 24F / 384f * canvas.width + titlePaint.textSize = 24F } else if (title.length <= 6) { - titlePaint.textSize = 16F / 384f * canvas.width + titlePaint.textSize = 16F } else { - titlePaint.textSize = 12F / 384f * canvas.width + titlePaint.textSize = 12F } titlePaint.getTextBounds(title, 0, title.length, titleBounds) @@ -224,20 +317,20 @@ class VerticalComplication(private val context: Context) : CanvasComplication { iconOffsetY = (height - iconBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - iconOffsetY += 6f / 384f * canvas.width + iconOffsetY += 6f if (isBattery) { iconOffsetY = iconOffsetY.toInt().toFloat() } - textOffsetY += 6f / 384f * canvas.width + textOffsetY += 6f } else if (title != null) { val height = titleBounds.height() + textBounds.height() titleOffsetY = (height - titleBounds.height()).toFloat() / 2f textOffsetY = (height - textBounds.height()).toFloat() / 2f - titleOffsetY += 6f / 384f * canvas.width - textOffsetY += 6f / 384f * canvas.width + titleOffsetY += 6f + textOffsetY += 6f } @@ -246,15 +339,6 @@ class VerticalComplication(private val context: Context) : CanvasComplication { - val cacheBitmap = Bitmap.createBitmap( - canvas.width, - canvas.height, - Bitmap.Config.ARGB_8888 - ) - val bitmapCanvas = Canvas(cacheBitmap) - - - if (icon != null) { val dstRect = RectF( bounds.exactCenterX() - iconBounds.width() / 2, @@ -263,9 +347,9 @@ class VerticalComplication(private val context: Context) : CanvasComplication { bounds.exactCenterY() + iconBounds.height() / 2 - iconOffsetY, ) - bitmapCanvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) + canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) } else if (title != null) { - bitmapCanvas.drawText( + canvas.drawText( title, bounds.exactCenterX() - titleBounds.width() / 2, bounds.exactCenterY() + titleBounds.height() / 2 - titleOffsetY, @@ -277,7 +361,7 @@ class VerticalComplication(private val context: Context) : CanvasComplication { val prefix = "".padStart(prefixLen, '0') prefixPaint.textSize = textPaint.textSize - bitmapCanvas.drawText( + canvas.drawText( prefix, bounds.exactCenterX() - textBounds.width() / 2, bounds.exactCenterY() + textBounds.height() / 2 + textOffsetY, @@ -285,73 +369,13 @@ class VerticalComplication(private val context: Context) : CanvasComplication { ) } - bitmapCanvas.drawText( + canvas.drawText( text, bounds.exactCenterX() - textBounds.width() / 2, bounds.exactCenterY() + textBounds.height() / 2 + textOffsetY, textPaint ) - - - canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { - canvas.drawBitmap( - cacheBitmap, - 0f, - 0f, - Paint(), - ) - } - - - - - - - - return - - canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { - - if (icon != null) { - val dstRect = RectF( - bounds.exactCenterX() - iconBounds.width() / 2, - bounds.exactCenterY() - iconBounds.height() / 2 - iconOffsetY, - bounds.exactCenterX() + iconBounds.width() / 2, - bounds.exactCenterY() + iconBounds.height() / 2 - iconOffsetY, - ) - - canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) - } else if (title != null) { - canvas.drawText( - title, - bounds.exactCenterX() - titleBounds.width() / 2, - bounds.exactCenterY() + titleBounds.height() / 2 - titleOffsetY, - titlePaint - ) - } - - if (prefixLen > 0) { - val prefix = "".padStart(prefixLen, '0') - prefixPaint.textSize = textPaint.textSize - - canvas.drawText( - prefix, - bounds.exactCenterX() - textBounds.width() / 2, - bounds.exactCenterY() + textBounds.height() / 2 + textOffsetY, - prefixPaint - ) - } - - canvas.drawText( - text, - bounds.exactCenterX() - textBounds.width() / 2, - bounds.exactCenterY() + textBounds.height() / 2 + textOffsetY, - textPaint - ) - - } - } private fun renderMonochromaticImageComplication( From 33eaa3087887730b6bd62f105fd926bd896c8a45 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Fri, 16 Feb 2024 21:32:01 +0200 Subject: [PATCH 25/52] Minor bug fixes --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 16 +- .../rdnt/m8face/utils/ComplicationUtils.kt | 5 +- .../m8face/utils/HorizontalComplication.kt | 4 +- .../utils/HorizontalTextComplication.kt | 4 +- .../dev/rdnt/m8face/utils/IconComplication.kt | 171 ------------------ .../rdnt/m8face/utils/VerticalComplication.kt | 4 +- 6 files changed, 12 insertions(+), 192 deletions(-) delete mode 100644 app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 208e691..eef017b 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -54,7 +54,6 @@ import dev.rdnt.m8face.utils.DEBUG_SETTING import dev.rdnt.m8face.utils.DETAILED_AMBIENT_SETTING import dev.rdnt.m8face.utils.HorizontalComplication import dev.rdnt.m8face.utils.HorizontalTextComplication -import dev.rdnt.m8face.utils.IconComplication import dev.rdnt.m8face.utils.LAYOUT_STYLE_SETTING import dev.rdnt.m8face.utils.MILITARY_TIME_SETTING import dev.rdnt.m8face.utils.SECONDS_STYLE_SETTING @@ -563,9 +562,6 @@ class WatchCanvasRenderer( is HorizontalTextComplication -> (complication.renderer as HorizontalTextComplication).tertiaryColor = watchFaceColors.tertiaryColor - is IconComplication -> (complication.renderer as IconComplication).tertiaryColor = - watchFaceColors.tertiaryColor - else -> {} } } @@ -949,7 +945,8 @@ class WatchCanvasRenderer( val offsetX = if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id) interpolate(34f, 0f) else 0f - val scale = interpolate(16f / 14f, 1f) + + val scale = if (watchFaceData.detailedAmbient) 1f else interpolate(16f / 14f, 1f) for ((_, complication) in complicationSlotsManager.complicationSlots) { if (complication.enabled) { @@ -974,11 +971,6 @@ class WatchCanvasRenderer( (complication.renderer as HorizontalTextComplication).debug = debug } - is IconComplication -> { - (complication.renderer as IconComplication).opacity = opacity - (complication.renderer as IconComplication).debug = debug - } - else -> {} } @@ -1048,12 +1040,12 @@ class WatchCanvasRenderer( if (debug) { canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#aaf2e900") + this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), opacity) style = Paint.Style.STROKE strokeWidth = 2f }) val p2 = Paint() - p2.color = Color.parseColor("#aaf2e900") + p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), opacity) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f canvas.drawText( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index 888fc6b..07866b8 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -488,7 +488,6 @@ fun createComplicationSlotManager( context: Context, currentUserStyleRepository: CurrentUserStyleRepository, ): ComplicationSlotsManager { - val iconComplicationFactory = createIconComplicationFactory(context) val verticalComplicationFactory = createVerticalComplicationFactory(context) val horizontalComplicationFactory = createHorizontalComplicationFactory(context) val invisibleComplicationFactory = createInvisibleComplicationFactory(context) @@ -732,7 +731,7 @@ fun createComplicationSlotManager( val leftIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = FOCUS_LEFT_ICON_COMPLICATION_ID, - canvasComplicationFactory = iconComplicationFactory, + canvasComplicationFactory = verticalComplicationFactory, supportedTypes = listOf( ComplicationType.MONOCHROMATIC_IMAGE, ComplicationType.SMALL_IMAGE @@ -755,7 +754,7 @@ fun createComplicationSlotManager( val rightIconComplication = ComplicationSlot.createRoundRectComplicationSlotBuilder( id = FOCUS_RIGHT_ICON_COMPLICATION_ID, - canvasComplicationFactory = iconComplicationFactory, + canvasComplicationFactory = verticalComplicationFactory, supportedTypes = listOf( ComplicationType.MONOCHROMATIC_IMAGE, ComplicationType.SMALL_IMAGE diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index 7f75ceb..f85a1ba 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -145,12 +145,12 @@ class HorizontalComplication(private val context: Context) : CanvasComplication private fun renderDebug(canvas: Canvas, bounds: Rect) { if (debug) { canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#aa02d7f2") + this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) style = Paint.Style.STROKE strokeWidth = 2f }) val p2 = Paint() - p2.color = Color.parseColor("#aa02d7f2") + p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f canvas.drawText( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt index 6cdc0ca..74e76c3 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt @@ -155,12 +155,12 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat private fun renderDebug(canvas: Canvas, bounds: Rect) { if (debug) { canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#aa02d7f2") + this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) style = Paint.Style.STROKE strokeWidth = 2f }) val p2 = Paint() - p2.color = Color.parseColor("#aa02d7f2") + p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f canvas.drawText( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt deleted file mode 100644 index 446d52e..0000000 --- a/app/src/main/java/dev/rdnt/m8face/utils/IconComplication.kt +++ /dev/null @@ -1,171 +0,0 @@ -package dev.rdnt.m8face.utils - -import android.content.Context -import android.graphics.* -import android.util.Log -import androidx.core.content.ContextCompat -import androidx.core.graphics.ColorUtils -import androidx.core.graphics.drawable.toBitmap -import androidx.core.graphics.toRect -import androidx.core.graphics.withRotation -import androidx.wear.watchface.CanvasComplication -import androidx.wear.watchface.CanvasComplicationFactory -import androidx.wear.watchface.RenderParameters -import androidx.wear.watchface.complications.data.* -import dev.rdnt.m8face.BitmapCacheEntry -import dev.rdnt.m8face.R -import java.time.Instant -import java.time.ZonedDateTime - -class IconComplication(private val context: Context) : CanvasComplication { - private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() - - var debug: Boolean = false; - - var tertiaryColor: Int = Color.parseColor("#8888bb") - set(tertiaryColor) { - field = tertiaryColor - iconPaint.colorFilter = PorterDuffColorFilter(tertiaryColor, PorterDuff.Mode.SRC_IN) - } - - var opacity: Float = 1f - set(opacity) { - field = opacity - - val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, opacity) - - iconPaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) - imagePaint.alpha = (opacity * 255).toInt() - } - - private val iconPaint = Paint().apply { - colorFilter = PorterDuffColorFilter(tertiaryColor, PorterDuff.Mode.SRC_IN) - } - - private val imagePaint = Paint() - - override fun render( - canvas: Canvas, - bounds: Rect, - zonedDateTime: ZonedDateTime, - renderParameters: RenderParameters, - slotId: Int - ) { - if (bounds.isEmpty) return - - if (debug) { - canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#aa02d7f2") - style = Paint.Style.STROKE - strokeWidth = 2f - }) - val p2 = Paint() - p2.color = Color.parseColor("#aa02d7f2") - p2.typeface = context.resources.getFont(R.font.m8stealth57) - p2.textSize = 8f - canvas.drawText( - "r ${bitmapCache.loads}", - bounds.left + 3f, - bounds.bottom - 12f, - p2, - ) - canvas.drawText( - "w ${bitmapCache.renders}", - bounds.left + 3f, - bounds.bottom - 3f, - p2, - ) - } - - when (data.type) { - ComplicationType.MONOCHROMATIC_IMAGE -> { - renderMonochromaticImageComplication( - canvas, - bounds, - data as MonochromaticImageComplicationData - ) - } - - ComplicationType.SMALL_IMAGE -> { - renderSmallImageComplication(canvas, bounds, data as SmallImageComplicationData) - } - - else -> return - } - } - - private fun renderMonochromaticImageComplication( - canvas: Canvas, - bounds: Rect, - data: MonochromaticImageComplicationData, - ) { - val icon: Bitmap - val iconBounds: Rect - - val drawable = data.monochromaticImage.image.loadDrawable(context) ?: return - - val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.75f).toInt() - - icon = drawable.toBitmap(size, size) - iconBounds = Rect(0, 0, size, size) - - val dstRect = RectF( - bounds.exactCenterX() - iconBounds.width() / 2, - bounds.exactCenterY() - iconBounds.height() / 2, - bounds.exactCenterX() + iconBounds.width() / 2, - bounds.exactCenterY() + iconBounds.height() / 2, - ) - - canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) - } - - private fun renderSmallImageComplication( - canvas: Canvas, - bounds: Rect, - data: SmallImageComplicationData, - ) { - val icon: Bitmap - val iconBounds: Rect - - val drawable = data.smallImage.image.loadDrawable(context) ?: return - - val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.75f).toInt() - - icon = drawable.toBitmap(size, size) - iconBounds = Rect(0, 0, size, size) - - val dstRect = RectF( - bounds.exactCenterX() - iconBounds.width() / 2, - bounds.exactCenterY() - iconBounds.height() / 2, - bounds.exactCenterX() + iconBounds.width() / 2, - bounds.exactCenterY() + iconBounds.height() / 2, - ) - - canvas.drawBitmap(icon, iconBounds, dstRect, imagePaint) - } - - override fun drawHighlight( - canvas: Canvas, - bounds: Rect, - boundsType: Int, - zonedDateTime: ZonedDateTime, - color: Int - ) { - // Rendering of highlights - } - - private var data: ComplicationData = NoDataComplicationData() - - override fun getData(): ComplicationData = data - - override fun loadData( - complicationData: ComplicationData, - loadDrawablesAsynchronous: Boolean - ) { - data = complicationData - } -} - -fun createIconComplicationFactory(context: Context) = CanvasComplicationFactory { _, _ -> - IconComplication(context) -} diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index b2064c9..075a7d9 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -204,12 +204,12 @@ class VerticalComplication(private val context: Context) : CanvasComplication { private fun renderDebug(canvas: Canvas, bounds: Rect) { if (debug) { canvas.drawRect(bounds, Paint().apply { - this.color = Color.parseColor("#aa02d7f2") + this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) style = Paint.Style.STROKE strokeWidth = 2f }) val p2 = Paint() - p2.color = Color.parseColor("#aa02d7f2") + p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f canvas.drawText( From 18322419feaad9826d00ee7121540f51459648fd Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Fri, 16 Feb 2024 22:20:51 +0200 Subject: [PATCH 26/52] Remove some analog seconds elements, misc --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 30 +++++++++---------- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index eef017b..bba8d71 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -879,7 +879,7 @@ class WatchCanvasRenderer( drawText( canvas, bounds, - zonedDateTime.second.toString().padStart(2, '0'), + if (ambientEnterAnimator.isRunning || isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0'), secondPaint, opacity, secondsOffsetX, @@ -944,7 +944,7 @@ class WatchCanvasRenderer( // opacity = 1f val offsetX = - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id) interpolate(34f, 0f) else 0f + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate(34f, 0f) else 0f val scale = if (watchFaceData.detailedAmbient) 1f else interpolate(16f / 14f, 1f) @@ -1084,7 +1084,7 @@ class WatchCanvasRenderer( secondHandPaint.alpha = (this.easeInOutCirc(this.drawProperties.timeScale) * secondHandPaint.alpha).toInt() - val transitionOffset: Float = this.easeInOutCirc(1 - this.drawProperties.timeScale) * 16f + val transitionOffset: Float = this.easeInOutCirc(1 - this.drawProperties.timeScale) * -16f * 0 // canvas.withRotation(secondsRotation, bounds.exactCenterX(), bounds.exactCenterY()) { // canvas.drawRect( @@ -1113,17 +1113,17 @@ class WatchCanvasRenderer( if (i.mod(90f) == 0f) { // cardinals color = watchFaceColors.primaryColor maxSize = 12f - weight = 1.75f + weight = 2f minAlpha = maxAlpha / 4f } else if (i.mod(30f) == 0f) { // intermediates color = watchFaceColors.secondaryColor maxSize = 10f - weight = 1.75f + weight = 2f minAlpha = maxAlpha / 4f } else { color = watchFaceColors.tertiaryColor maxSize = 8f - weight = 1.5f + weight = 1.75f minAlpha = maxAlpha / 8f } @@ -1219,20 +1219,20 @@ class WatchCanvasRenderer( var centerY = 8f * this.easeInOutCirc(this.drawProperties.timeScale) val secondHandSize = 3.5f * this.easeInOutCirc(this.drawProperties.timeScale) - val transitionOffset = this.easeInOutCirc(1 - this.drawProperties.timeScale) * 24f + val transitionOffset = this.easeInOutCirc(1 - this.drawProperties.timeScale) * 24f * 0 val secondHandPaint = Paint(this.secondHandPaint) secondHandPaint.alpha = (this.easeInOutCirc(this.drawProperties.timeScale) * secondHandPaint.alpha).toInt() - canvas.withRotation(rotation, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawCircle( - bounds.centerX().toFloat(), - (centerY + transitionOffset), - secondHandSize / 384F * bounds.width(), - secondHandPaint - ) - } +// canvas.withRotation(rotation, bounds.exactCenterX(), bounds.exactCenterY()) { +// canvas.drawCircle( +// bounds.centerX().toFloat(), +// (centerY + transitionOffset), +// secondHandSize / 384F * bounds.width(), +// secondHandPaint +// ) +// } val maxAlpha = 255f diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c103419..0c5c480 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanist-pager = "0.32.0" activity-compose = "1.8.0" -android-gradle-plugin = "8.2.2" +android-gradle-plugin = "8.3.0-rc02" androidx-activity = "1.8.0" androidx-lifecycle = "2.6.2" androidx-wear-watchface = "1.1.1" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8612e3f..309b4e1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 0685933ddaaf916d185bb7a7456237aa08a77cf3 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sat, 9 Mar 2024 15:05:04 +0200 Subject: [PATCH 27/52] minor bug fixes --- app/build.gradle | 2 +- app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 8 ++++---- .../dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a39ff0b..9fdf042 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,7 +28,7 @@ android { minSdk 28 targetSdk 33 versionCode 56 - versionName '2.12.1' + versionName '3.0.0' } buildFeatures { diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index bba8d71..8314d8e 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -923,11 +923,11 @@ class WatchCanvasRenderer( } -// if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && -// watchFaceData.detailedAmbient || drawProperties.timeScale != 0f -// ) { + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && + (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) + ) { drawComplications(canvas, zonedDateTime) -// } + } } diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index ab0d05f..47c5c99 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -469,7 +469,7 @@ fun ConfigScaffold( } - if (pagerState.currentPage == 2) { // special background for third page (ambient style) + if (pagerState.currentPage == 3) { // special background for third page (ambient style) var id = 0 val current = ambientStyles.indexOfFirst { it.id == (stateHolder.uiState.value as WatchFaceConfigStateHolder.EditWatchFaceUiState.Success).userStylesAndPreview.ambientStyleId } @@ -539,7 +539,7 @@ fun ConfigScaffold( Preview(bitmap) } - Overlay(pagerState) +// Overlay(pagerState) // TODO fix LaunchedEffect(pagerState.currentPage) { Log.d("Editor", "LaunchedEffect(${pagerState.currentPage})") From 085d47f418d50a60ab605469cd0fed1c2a23c887 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sat, 16 Mar 2024 23:03:36 +0200 Subject: [PATCH 28/52] Rendering performance optimizations, simplify transform/translate/opacity scaling --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 379 +++++++++--------- .../m8face/utils/HorizontalComplication.kt | 61 +-- .../utils/HorizontalTextComplication.kt | 40 +- .../rdnt/m8face/utils/VerticalComplication.kt | 51 +-- 4 files changed, 263 insertions(+), 268 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 8314d8e..b848062 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -16,7 +16,9 @@ package dev.rdnt.m8face import android.animation.AnimatorSet +import android.animation.Keyframe import android.animation.ObjectAnimator +import android.animation.PropertyValuesHolder import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas @@ -25,7 +27,6 @@ import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF import android.util.FloatProperty -import android.util.Log import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep @@ -34,6 +35,7 @@ import androidx.core.animation.doOnStart import androidx.core.graphics.ColorUtils import androidx.core.graphics.withRotation import androidx.core.graphics.withScale +import androidx.core.graphics.withTranslation import androidx.wear.watchface.ComplicationSlotsManager import androidx.wear.watchface.Renderer import androidx.wear.watchface.WatchState @@ -61,6 +63,7 @@ import dev.rdnt.m8face.utils.VerticalComplication import java.time.Duration import java.time.ZonedDateTime import kotlin.math.pow +import kotlin.math.round import kotlin.math.sqrt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -254,79 +257,55 @@ class WatchCanvasRenderer( private val bitmapCache: BitmapCache = BitmapCache() - private var interactiveFrameDelay: Long = 60000 + private var interactiveFrameDelay: Long = 16 private val ambientExitAnimator = AnimatorSet().apply { - val linearOutSlow = AnimationUtils.loadInterpolator( - context, android.R.interpolator.linear_out_slow_in + interpolator = AnimationUtils.loadInterpolator( + context, android.R.interpolator.accelerate_decelerate ) play( ObjectAnimator.ofFloat( - drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale, 1.0f + drawProperties, DrawProperties.TIME_SCALE, 0f, 1.0f ).apply { duration = ambientTransitionMs - interpolator = linearOutSlow - setAutoCancel(false) + setAutoCancel(true) doOnStart { -// updateRefreshRate() interactiveDrawModeUpdateDelayMillis = 16 - Log.d("@@@", "START") } doOnEnd { interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay -// updateRefreshRate() - Log.d("@@@", "END") } }, ) } private val ambientEnterAnimator = AnimatorSet().apply { - val linearOutSlow = AnimationUtils.loadInterpolator( - context, android.R.interpolator.linear_out_slow_in + interpolator = AnimationUtils.loadInterpolator( + context, android.R.interpolator.accelerate_decelerate ) + + val keyframes = arrayOf( + Keyframe.ofFloat(0f, 1f), + Keyframe.ofFloat(0.9f, 0f), + Keyframe.ofFloat(1f, 0f), + ) + + val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( + DrawProperties.TIME_SCALE, *keyframes + ) + play( - ObjectAnimator.ofFloat( - drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale, 0.0f - ).apply { + ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { duration = ambientTransitionMs - interpolator = linearOutSlow - setAutoCancel(false) + setAutoCancel(true) doOnStart { -// updateRefreshRate() - interactiveDrawModeUpdateDelayMillis = 16 - Log.d("@@@", "START") + interactiveDrawModeUpdateDelayMillis = 16 // TODO: consider 33 for better battery life } doOnEnd { - interactiveDrawModeUpdateDelayMillis = 60000 -// updateRefreshRate() - Log.d("@@@", "END") + interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay } }, ) - -// val linearOutSlow = AnimationUtils.loadInterpolator( -// context, android.R.interpolator.linear_out_slow_in -// ) -// -// val keyframes = arrayOf( -// Keyframe.ofFloat(0f, drawProperties.timeScale), -// Keyframe.ofFloat(0.9f, 0f), -// Keyframe.ofFloat(1f, 0f) -// ) -// -// val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( -// DrawProperties.TIME_SCALE, *keyframes -// ) -// -// play( -// ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { -//// duration = ambientTransitionMs * 5 / 9 -// duration = ambientTransitionMs -// interpolator = linearOutSlow -// setAutoCancel(false) -// }, -// ) } init { @@ -343,22 +322,23 @@ class WatchCanvasRenderer( if (!watchState.isHeadless) { if (isAmbient) { + ambientExitAnimator.cancel() + ambientEnterAnimator.cancel() + ambientEnterAnimator.setupStartValues() ambientEnterAnimator.start() -// ambientExitAnimator.cancel() -// drawProperties.timeScale = 0f } else { + + ambientExitAnimator.cancel() ambientEnterAnimator.cancel() + ambientExitAnimator.setupStartValues() ambientExitAnimator.start() } } else { - ambientExitAnimator.setupStartValues() drawProperties.timeScale = 0f } - -// updateRefreshRate() } } } @@ -636,23 +616,10 @@ class WatchCanvasRenderer( if (watchFaceData.detailedAmbient) { -34f } else { - interpolate(0f, -34f) + -34f +// interpolate(0f, -34f) } - } - else -> { - 0f - } - } - - val minutesOffsetX: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> { - if (watchFaceData.detailedAmbient) { - -34f - 11f - } else { - interpolate(0f, -34f - 11f) - } } else -> { @@ -660,6 +627,21 @@ class WatchCanvasRenderer( } } +// val minutesOffsetX: Float +// get() = when (watchFaceData.layoutStyle.id) { +// LayoutStyle.SPORT.id -> { +// if (watchFaceData.detailedAmbient) { +// -34f - 11f +// } else { +// interpolate(0f, -34f - 11f) +// } +// } +// +// else -> { +// 0f +// } +// } + val hourOffsetY: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { @@ -724,7 +706,8 @@ class WatchCanvasRenderer( if (watchFaceData.detailedAmbient) { 95f } else { - interpolate(129f, 95f) + 95f +// interpolate(129f, 95f) } } @@ -749,7 +732,8 @@ class WatchCanvasRenderer( get() = if (watchFaceData.detailedAmbient) { 84f } else { - interpolate(84f+34f, 84f) + 84f +// interpolate(84f+34f, 84f) } val secondsTextSize: Float @@ -760,27 +744,27 @@ class WatchCanvasRenderer( else -> 0f } - val secondsTextScale: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 18f / 14f - } - - else -> { - if (watchFaceData.detailedAmbient) { - 14f / 14f - } else { - interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) - } - } - } +// val secondsTextScale: Float +// get() = when (watchFaceData.layoutStyle.id) { +// LayoutStyle.FOCUS.id -> { +// 18f / 14f +// } +// +// else -> { +// if (watchFaceData.detailedAmbient) { +// 14f / 14f +// } else { +// interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) +// } +// } +// } - val ampmTextScale: Float - get() = if (watchFaceData.detailedAmbient) { - 14f / 14f - } else { - interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) - } +// val ampmTextScale: Float +// get() = if (watchFaceData.detailedAmbient) { +// 14f / 14f +// } else { +// interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) +// } fun getHour(zonedDateTime: ZonedDateTime): Int { if (is24Format) { @@ -820,7 +804,7 @@ class WatchCanvasRenderer( p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f - val text = "f $frame" + val text = "f $frame s ${"%.2f".format(drawProperties.timeScale)}" val textBounds = Rect() p2.getTextBounds(text, 0, text.length, textBounds) @@ -833,100 +817,98 @@ class WatchCanvasRenderer( ) } -// val p2 = Paint() -// p2.color = Color.WHITE -// canvas.drawText( -// "frame $renders ${ambientEnterAnimator.isRunning} ${ambientExitAnimator.isRunning} ${interactiveDrawModeUpdateDelayMillis}", -// 192f, -// bounds.height().toFloat()-192f, -// p2, -// ) + canvas.withScale(timeTextScale, timeTextScale, bounds.exactCenterX(), bounds.exactCenterY()) { + val offsetX = if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate(34f, 0f) else 0f + canvas.withTranslation(offsetX, 0f) { - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - drawText( - canvas, - bounds, - getHour(zonedDateTime).toString().padStart(2, '0'), - hourPaint, - 1f, - timeOffsetX, - hourOffsetY, - timeTextSize, - timeTextScale, - HOURS_BITMAP_KEY - ) - - drawText( - canvas, - bounds, - zonedDateTime.minute.toString().padStart(2, '0'), - minutePaint, - 1f, - timeOffsetX, - minuteOffsetY, - timeTextSize, - timeTextScale, - MINUTES_BITMAP_KEY - ) + val compBmp = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) - if (shouldDrawSeconds) { - var opacity = - if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( - drawProperties.timeScale - ) -// opacity = 1f + val compCanvas = Canvas(compBmp) + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { drawText( canvas, bounds, - if (ambientEnterAnimator.isRunning || isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0'), - secondPaint, - opacity, - secondsOffsetX, - secondsOffsetY, - secondsTextSize, - secondsTextScale, - SECONDS_BITMAP_KEY, + getHour(zonedDateTime).toString().padStart(2, '0'), + hourPaint, + 1f, + timeOffsetX, + hourOffsetY, + timeTextSize, + HOURS_BITMAP_KEY ) - } - - if (shouldDrawAmPm) { - var opacity = - if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( - drawProperties.timeScale - ) -// opacity = 1f drawText( canvas, bounds, - getAmPm(zonedDateTime).uppercase(), - secondPaint, - opacity, - ampmOffsetX, - 24f, - 5f, - ampmTextScale, - AMPM_BITMAP_KEY, + zonedDateTime.minute.toString().padStart(2, '0'), + minutePaint, + 1f, + timeOffsetX, + minuteOffsetY, + timeTextSize, + MINUTES_BITMAP_KEY ) - } - when (watchFaceData.secondsStyle.id) { - SecondsStyle.DASHES.id -> { - drawDashes(canvas, bounds, zonedDateTime) + if (shouldDrawSeconds) { + drawText( + compCanvas, + bounds, + if (watchFaceData.detailedAmbient && drawProperties.timeScale == 0f) "M8" else zonedDateTime.second.toString() + .padStart(2, '0'), + secondPaint, + 1f, + secondsOffsetX, + secondsOffsetY, + secondsTextSize, + SECONDS_BITMAP_KEY, + ) } - SecondsStyle.DOTS.id -> { - drawDots(canvas, bounds, zonedDateTime) + if (shouldDrawAmPm) { + drawText( + compCanvas, + bounds, + getAmPm(zonedDateTime).uppercase(), + secondPaint, + 1f, + ampmOffsetX, + 24f, + 5f, + AMPM_BITMAP_KEY, + ) } - } - } + when (watchFaceData.secondsStyle.id) { + SecondsStyle.DASHES.id -> { + drawDashes(compCanvas, bounds, zonedDateTime) + } + + SecondsStyle.DOTS.id -> { + drawDots(compCanvas, bounds, zonedDateTime) + } + } + + } + + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && + (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) + ) { + drawComplications(compCanvas, zonedDateTime) + } + + val opacity = if (watchFaceData.detailedAmbient) interpolate(.5f, 1f) else interpolate(0f, 1f) + + canvas.drawBitmap( + compBmp, + bounds.left.toFloat(), + bounds.top.toFloat(), + Paint().apply { alpha = (opacity * 255).toInt() }, + ) - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) - ) { - drawComplications(canvas, zonedDateTime) + } } } @@ -937,37 +919,42 @@ class WatchCanvasRenderer( // ----- All drawing functions ----- private fun drawComplications(canvas: Canvas, zonedDateTime: ZonedDateTime) { - var opacity = - if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( - drawProperties.timeScale - ) +// var opacity = +// if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( +// drawProperties.timeScale +// ) // opacity = 1f - val offsetX = - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate(34f, 0f) else 0f +// val offsetX = +// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate(34f, 0f) else 0f +// val offsetX = 0f - val scale = if (watchFaceData.detailedAmbient) 1f else interpolate(16f / 14f, 1f) +// val maxScale = if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f +// var scale = if (watchFaceData.detailedAmbient) 1f else interpolate(maxScale, 1f) +// if (watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id) { +// scale = 1f +// } for ((_, complication) in complicationSlotsManager.complicationSlots) { if (complication.enabled) { when (complication.renderer) { is VerticalComplication -> { - (complication.renderer as VerticalComplication).opacity = opacity - (complication.renderer as VerticalComplication).scale = scale +// (complication.renderer as VerticalComplication).opacity = opacity +// (complication.renderer as VerticalComplication).scale = scale (complication.renderer as VerticalComplication).debug = debug } is HorizontalComplication -> { - (complication.renderer as HorizontalComplication).opacity = opacity - (complication.renderer as HorizontalComplication).offsetX = offsetX - (complication.renderer as HorizontalComplication).scale = scale +// (complication.renderer as HorizontalComplication).opacity = opacity +// (complication.renderer as HorizontalComplication).offsetX = offsetX +// (complication.renderer as HorizontalComplication).scale = scale (complication.renderer as HorizontalComplication).debug = debug } is HorizontalTextComplication -> { - (complication.renderer as HorizontalTextComplication).opacity = opacity - (complication.renderer as HorizontalTextComplication).offsetX = offsetX - (complication.renderer as HorizontalTextComplication).scale = scale +// (complication.renderer as HorizontalTextComplication).opacity = opacity +// (complication.renderer as HorizontalTextComplication).offsetX = offsetX +// (complication.renderer as HorizontalTextComplication).scale = scale (complication.renderer as HorizontalTextComplication).debug = debug } @@ -988,19 +975,18 @@ class WatchCanvasRenderer( offsetX: Float, offsetY: Float, textSize: Float, - textScale: Float, cacheKey: String, ) { - val bitmap = renderText(text, paint, textSize, paint.color, opacity, cacheKey) - - canvas.withScale(textScale, textScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.drawBitmap( - bitmap, - 192f - bitmap.width / 2 + offsetX, - 192f - bitmap.height / 2 + offsetY, - Paint(), - ) - } + val bitmap = renderText(text, paint, textSize, paint.color, cacheKey) + + canvas.drawBitmap( + bitmap, + 192f - bitmap.width / 2 + offsetX, + 192f - bitmap.height / 2 + offsetY, + Paint().apply { + alpha = (opacity * 255).toInt() + }, + ) } private fun renderText( @@ -1008,10 +994,9 @@ class WatchCanvasRenderer( paint: Paint, textSize: Float, color: Int, - opacity: Float, cacheKey: String, ): Bitmap { - val hash = "${text},${textSize},${color},${opacity},${debug}" + val hash = "${text},${textSize},${color},${debug}" val cached = bitmapCache.get(cacheKey, hash) if (cached != null) { @@ -1020,7 +1005,7 @@ class WatchCanvasRenderer( val p = Paint(paint) p.textSize *= textSize - p.color = ColorUtils.blendARGB(Color.TRANSPARENT, color, opacity) + p.color = color val textBounds = Rect() p.getTextBounds(text, 0, text.length, textBounds) @@ -1040,12 +1025,12 @@ class WatchCanvasRenderer( if (debug) { canvas.drawRect(bounds, Paint().apply { - this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), opacity) + this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) style = Paint.Style.STROKE strokeWidth = 2f }) val p2 = Paint() - p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), opacity) + p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f canvas.drawText( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index f85a1ba..8d39dad 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -9,9 +9,11 @@ import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.graphics.RectF +import android.util.Log import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.toRectF import androidx.core.graphics.withScale import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory @@ -28,6 +30,10 @@ import java.time.ZonedDateTime class HorizontalComplication(private val context: Context) : CanvasComplication { private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() + init { + Log.d("HorizontalComplication", "Constructor ran") + } + var debug: Boolean = false; var tertiaryColor: Int = Color.parseColor("#8888bb") @@ -40,24 +46,24 @@ class HorizontalComplication(private val context: Context) : CanvasComplication prefixPaint.alpha = 100 } - var opacity: Float = 1f - set(opacity) { - field = opacity - - val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, opacity) - - textPaint.color = color - titlePaint.color = color - - iconPaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) - - prefixPaint.color = color - prefixPaint.alpha = 100 - } - - var offsetX: Float = 0f - - var scale: Float = 1f +// var opacity: Float = 1f +// set(opacity) { +// field = opacity +// +// val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, 1f) +// +// textPaint.color = color +// titlePaint.color = color +// +// iconPaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) +// +// prefixPaint.color = color +// prefixPaint.alpha = 100 +// } + +// var offsetX: Float = 0f + +// var scale: Float = 1f private val textPaint = Paint().apply { isAntiAlias = true @@ -104,14 +110,17 @@ class HorizontalComplication(private val context: Context) : CanvasComplication else -> return } - canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { +// canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + renderDebug(canvas, bounds.toRectF()) + canvas.drawBitmap( bitmap, - bounds.left + offsetX, + bounds.left.toFloat(), bounds.top.toFloat(), Paint(), +// Paint().apply { alpha = (opacity * 255).toInt() }, ) - } +// } } @@ -119,7 +128,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication bounds: Rect, data: ShortTextComplicationData ): Bitmap { - val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${opacity},${debug}" + val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${debug}" val cached = bitmapCache.get(hash) if (cached != null) { @@ -135,22 +144,20 @@ class HorizontalComplication(private val context: Context) : CanvasComplication renderShortTextComplication(bitmapCanvas, rect, data) - renderDebug(bitmapCanvas, rect) - bitmapCache.set(hash, bitmap) return bitmap } - private fun renderDebug(canvas: Canvas, bounds: Rect) { + private fun renderDebug(canvas: Canvas, bounds: RectF) { if (debug) { canvas.drawRect(bounds, Paint().apply { - this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) + this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) style = Paint.Style.STROKE strokeWidth = 2f }) val p2 = Paint() - p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) + p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f canvas.drawText( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt index 74e76c3..c2ed1e7 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt @@ -13,6 +13,7 @@ import android.util.Log import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.toRectF import androidx.core.graphics.withScale import androidx.core.graphics.withTranslation import androidx.wear.watchface.CanvasComplication @@ -38,18 +39,18 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat textPaint.color = tertiaryColor } - var opacity: Float = 1f - set(opacity) { - field = opacity +// var opacity: Float = 1f +// set(opacity) { +// field = opacity +// +// val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, 1f) +// +// textPaint.color = color +// } - val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, opacity) +// var offsetX: Float = 0f - textPaint.color = color - } - - var offsetX: Float = 0f - - var scale: Float = 0f +// var scale: Float = 0f private val textPaint = Paint().apply { isAntiAlias = true @@ -79,21 +80,24 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat else -> return } - canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { +// canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + renderDebug(canvas, bounds.toRectF()) + canvas.drawBitmap( bitmap, - bounds.left + offsetX, + bounds.left.toFloat(), bounds.top.toFloat(), Paint(), +// Paint().apply { alpha = (opacity * 255).toInt() }, ) - } +// } } private fun drawShortTextComplication( bounds: Rect, data: ShortTextComplicationData ): Bitmap { - val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${opacity},${debug}" + val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${debug}" val cached = bitmapCache.get(hash) if (cached != null) { @@ -109,8 +113,6 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat renderShortTextComplication(bitmapCanvas, rect, data) - renderDebug(bitmapCanvas, rect) - bitmapCache.set(hash, bitmap) return bitmap @@ -152,15 +154,15 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat ) } - private fun renderDebug(canvas: Canvas, bounds: Rect) { + private fun renderDebug(canvas: Canvas, bounds: RectF) { if (debug) { canvas.drawRect(bounds, Paint().apply { - this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) + this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) style = Paint.Style.STROKE strokeWidth = 2f }) val p2 = Paint() - p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) + p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f canvas.drawText( diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index 075a7d9..167c0c8 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -33,22 +33,22 @@ class VerticalComplication(private val context: Context) : CanvasComplication { prefixPaint.alpha = 100 } - var opacity: Float = 1f - set(opacity) { - field = opacity - - val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, opacity) - textPaint.color = color - titlePaint.color = color - - iconPaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) - imagePaint.alpha = (opacity * 255).toInt() - - prefixPaint.color = color - prefixPaint.alpha = 100 - } - - var scale: Float = 1f +// var opacity: Float = 1f +// set(opacity) { +// field = opacity +// +// val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, 1f) +// textPaint.color = color +// titlePaint.color = color +// +// iconPaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) +//// imagePaint.alpha = (opacity * 255).toInt() +// +// prefixPaint.color = color +// prefixPaint.alpha = 100 +// } + +// var scale: Float = 1f private val textPaint = Paint().apply { isAntiAlias = true @@ -110,21 +110,24 @@ class VerticalComplication(private val context: Context) : CanvasComplication { else -> return } - canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { +// canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { + renderDebug(canvas, bounds) + canvas.drawBitmap( bitmap, bounds.left.toFloat(), bounds.top.toFloat(), Paint(), +// Paint().apply { alpha = (opacity * 255).toInt() }, ) - } +// } } private fun drawShortTextComplication( bounds: Rect, data: ShortTextComplicationData ): Bitmap { - val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${opacity},${debug}" + val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${debug}" val cached = bitmapCache.get(hash) if (cached != null) { @@ -140,8 +143,6 @@ class VerticalComplication(private val context: Context) : CanvasComplication { renderShortTextComplication(bitmapCanvas, rect, data) - renderDebug(bitmapCanvas, rect) - bitmapCache.set(hash, bitmap) return bitmap @@ -151,7 +152,7 @@ class VerticalComplication(private val context: Context) : CanvasComplication { bounds: Rect, data: MonochromaticImageComplicationData ): Bitmap { - val hash = "${bounds},${data.monochromaticImage.image.resId},${tertiaryColor},${opacity},${debug}" + val hash = "${bounds},${data.monochromaticImage.image.resId},${tertiaryColor},${debug}" val cached = bitmapCache.get(hash) if (cached != null) { @@ -178,7 +179,7 @@ class VerticalComplication(private val context: Context) : CanvasComplication { bounds: Rect, data: SmallImageComplicationData ): Bitmap { - val hash = "${bounds},${data.smallImage.image.resId},${tertiaryColor},${opacity},${debug}" + val hash = "${bounds},${data.smallImage.image.resId},${tertiaryColor},${debug}" val cached = bitmapCache.get(hash) if (cached != null) { @@ -204,12 +205,12 @@ class VerticalComplication(private val context: Context) : CanvasComplication { private fun renderDebug(canvas: Canvas, bounds: Rect) { if (debug) { canvas.drawRect(bounds, Paint().apply { - this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) + this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) style = Paint.Style.STROKE strokeWidth = 2f }) val p2 = Paint() - p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), opacity) + p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f canvas.drawText( From a207ba872f255b7c5f0461b714dc22050ac14b91 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sat, 16 Mar 2024 23:31:53 +0200 Subject: [PATCH 29/52] minor fixes --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 114 +++++++++--------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index b848062..fe52e0e 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -63,7 +63,6 @@ import dev.rdnt.m8face.utils.VerticalComplication import java.time.Duration import java.time.ZonedDateTime import kotlin.math.pow -import kotlin.math.round import kotlin.math.sqrt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -76,7 +75,6 @@ import kotlinx.coroutines.launch private const val DEFAULT_INTERACTIVE_DRAW_MODE_UPDATE_DELAY_MILLIS: Long = 16 - /** * Renders watch face via data in Room database. Also, updates watch face state based on setting * changes by user via [userStyleRepository.addUserStyleListener()]. @@ -818,7 +816,11 @@ class WatchCanvasRenderer( } canvas.withScale(timeTextScale, timeTextScale, bounds.exactCenterX(), bounds.exactCenterY()) { - val offsetX = if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate(34f, 0f) else 0f + val offsetX = + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( + 34f, + 0f + ) else 0f canvas.withTranslation(offsetX, 0f) { val compBmp = Bitmap.createBitmap( @@ -827,79 +829,81 @@ class WatchCanvasRenderer( val compCanvas = Canvas(compBmp) - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - drawText( - canvas, - bounds, - getHour(zonedDateTime).toString().padStart(2, '0'), - hourPaint, - 1f, - timeOffsetX, - hourOffsetY, - timeTextSize, - HOURS_BITMAP_KEY - ) - - drawText( - canvas, - bounds, - zonedDateTime.minute.toString().padStart(2, '0'), - minutePaint, - 1f, - timeOffsetX, - minuteOffsetY, - timeTextSize, - MINUTES_BITMAP_KEY - ) - - if (shouldDrawSeconds) { + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { drawText( - compCanvas, + canvas, bounds, - if (watchFaceData.detailedAmbient && drawProperties.timeScale == 0f) "M8" else zonedDateTime.second.toString() - .padStart(2, '0'), - secondPaint, + getHour(zonedDateTime).toString().padStart(2, '0'), + hourPaint, 1f, - secondsOffsetX, - secondsOffsetY, - secondsTextSize, - SECONDS_BITMAP_KEY, + timeOffsetX, + hourOffsetY, + timeTextSize, + HOURS_BITMAP_KEY ) - } - if (shouldDrawAmPm) { drawText( - compCanvas, + canvas, bounds, - getAmPm(zonedDateTime).uppercase(), - secondPaint, + zonedDateTime.minute.toString().padStart(2, '0'), + minutePaint, 1f, - ampmOffsetX, - 24f, - 5f, - AMPM_BITMAP_KEY, + timeOffsetX, + minuteOffsetY, + timeTextSize, + MINUTES_BITMAP_KEY ) - } - when (watchFaceData.secondsStyle.id) { - SecondsStyle.DASHES.id -> { - drawDashes(compCanvas, bounds, zonedDateTime) + if (shouldDrawSeconds) { + drawText( + compCanvas, + bounds, + if (watchFaceData.detailedAmbient && isAmbient) "M8" else zonedDateTime.second.toString() + .padStart(2, '0'), + secondPaint, + 1f, + secondsOffsetX, + secondsOffsetY, + secondsTextSize, + SECONDS_BITMAP_KEY, + ) } - SecondsStyle.DOTS.id -> { - drawDots(compCanvas, bounds, zonedDateTime) + if (shouldDrawAmPm) { + drawText( + compCanvas, + bounds, + getAmPm(zonedDateTime).uppercase(), + secondPaint, + 1f, + ampmOffsetX, + 24f, + 5f, + AMPM_BITMAP_KEY, + ) + } + + when (watchFaceData.secondsStyle.id) { + SecondsStyle.DASHES.id -> { + drawDashes(compCanvas, bounds, zonedDateTime) + } + + SecondsStyle.DOTS.id -> { + drawDots(compCanvas, bounds, zonedDateTime) + } } - } } if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) - ) { + ) { drawComplications(compCanvas, zonedDateTime) } - val opacity = if (watchFaceData.detailedAmbient) interpolate(.5f, 1f) else interpolate(0f, 1f) + val scale = if (isAmbient) .75f else 1f +// val scale = interpolate(.75f, 1f) // TODO scaling opacity looks weird due to automatic brightness kicking in + val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) canvas.drawBitmap( compBmp, From 137b775794ccb5cdf125bd653221bdc29a222709 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 17 Mar 2024 01:18:37 +0200 Subject: [PATCH 30/52] add support for ambient styles (wip) --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 234 ++++++++++++++++-- 1 file changed, 208 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index fe52e0e..b362b1c 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -593,6 +593,24 @@ class WatchCanvasRenderer( } } + val timeTextSizeThick: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { +// 22f / 16f + 0f + } + + else -> { + if (watchFaceData.detailedAmbient) { +// 18f / 16f + 0f + } else { +// interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) + 18f / 16f + } + } + } + val timeTextScale: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { @@ -608,13 +626,45 @@ class WatchCanvasRenderer( } } + val timeTextScaleThick: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 18f / 18f + } + + else -> { + if (watchFaceData.detailedAmbient) { + 14f / 18f + } else { + interpolate(if (watchFaceData.bigAmbient) 18f / 18f else 16f / 18f, 14f / 18f) + } + } + } + val timeOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { if (watchFaceData.detailedAmbient) { - -34f + -35f + } else { + -35f +// interpolate(0f, -34f) + } + + } + + else -> { + 0f + } + } + + val timeOffsetXThick: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + if (watchFaceData.detailedAmbient) { + -35f/14f*18f } else { - -34f + -35f/14f*18f // interpolate(0f, -34f) } @@ -644,7 +694,7 @@ class WatchCanvasRenderer( get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { // 183f - 192f - -58f - 10f - 4f + -72f // -7f - 49f } @@ -652,6 +702,22 @@ class WatchCanvasRenderer( -56f // 185f - 192f // -7f - 49f +// interpolate(183f, 185f) + } + } + + val hourOffsetYThick: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { +// 183f - 192f + -72f +// -7f - 49f + } + + else -> { + -72f +// 185f - 192f +// -7f - 49f // interpolate(183f, 185f) } } @@ -684,6 +750,18 @@ class WatchCanvasRenderer( } } + val minuteOffsetYThick: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + 72f + } + + else -> { + 72f +// interpolate(327f, 297f) + } + } + val shouldDrawSeconds: Boolean get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.INFO1.id -> true @@ -815,30 +893,87 @@ class WatchCanvasRenderer( ) } - canvas.withScale(timeTextScale, timeTextScale, bounds.exactCenterX(), bounds.exactCenterY()) { - val offsetX = - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( - 34f, - 0f - ) else 0f - canvas.withTranslation(offsetX, 0f) { + var offsetX = + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( + 35f/14f*18f, + 0f + ) else 0f +// val offsetX = 0f - val compBmp = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) + val text14 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 144f + } - val compCanvas = Canvas(compBmp) + val text16 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57thin) + textSize = 9f + } + + val text18 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57thick) + textSize = 9f + } + + val hourText = when { + (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f) -> { + Paint(text16) + } + (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f) -> { + Paint(text18) + } + else -> { + Paint(text14) + } + } + + val minuteText14 = Paint(minutePaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 144f + } + + val minuteText16 = Paint(minutePaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57thin) + textSize = 9f + } + + val minuteText18 = Paint(minutePaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57thick) + textSize = 9f + } + + val minuteText = when { + (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f) -> { + Paint(minuteText16) + } + (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f) -> { + Paint(minuteText18) + } + else -> { + Paint(minuteText14) + } + } + + canvas.withScale(timeTextScaleThick, timeTextScaleThick, bounds.exactCenterX(), bounds.exactCenterY()) { + + canvas.withTranslation(offsetX, 0f) { if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { drawText( canvas, bounds, getHour(zonedDateTime).toString().padStart(2, '0'), - hourPaint, + hourText, + 1f, + timeOffsetXThick, + hourOffsetYThick, 1f, - timeOffsetX, - hourOffsetY, - timeTextSize, HOURS_BITMAP_KEY ) @@ -846,13 +981,60 @@ class WatchCanvasRenderer( canvas, bounds, zonedDateTime.minute.toString().padStart(2, '0'), - minutePaint, + minuteText, + 1f, + timeOffsetXThick, + minuteOffsetYThick, 1f, - timeOffsetX, - minuteOffsetY, - timeTextSize, MINUTES_BITMAP_KEY ) + } + } + } + + offsetX = + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( + 35f, + 0f + ) else 0f + + + + canvas.withScale(timeTextScale, timeTextScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withTranslation(offsetX, 0f) { + + val compBmp = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + + val compCanvas = Canvas(compBmp) + + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { +// drawText( +// canvas, +// bounds, +// getHour(zonedDateTime).toString().padStart(2, '0'), +// hourPaint, +// 1f, +// timeOffsetX, +// hourOffsetY, +// timeTextSize, +// HOURS_BITMAP_KEY +// ) + + + +// drawText( +// canvas, +// bounds, +// zonedDateTime.minute.toString().padStart(2, '0'), +// minutePaint, +// 1f, +// timeOffsetX, +// minuteOffsetY, +// timeTextSize, +// MINUTES_BITMAP_KEY +// ) if (shouldDrawSeconds) { drawText( @@ -1002,10 +1184,10 @@ class WatchCanvasRenderer( ): Bitmap { val hash = "${text},${textSize},${color},${debug}" - val cached = bitmapCache.get(cacheKey, hash) - if (cached != null) { - return cached - } +// val cached = bitmapCache.get(cacheKey, hash) +// if (cached != null) { +// return cached +// } val p = Paint(paint) p.textSize *= textSize From 0eb5ea935358edcbcbda8e11bc1041332ded30ac Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 17 Mar 2024 12:21:40 +0200 Subject: [PATCH 31/52] fix stuck animators --- app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index b362b1c..e867b46 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -266,7 +266,7 @@ class WatchCanvasRenderer( drawProperties, DrawProperties.TIME_SCALE, 0f, 1.0f ).apply { duration = ambientTransitionMs - setAutoCancel(true) +// setAutoCancel(true) doOnStart { interactiveDrawModeUpdateDelayMillis = 16 } @@ -295,7 +295,7 @@ class WatchCanvasRenderer( play( ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { duration = ambientTransitionMs - setAutoCancel(true) +// setAutoCancel(true) doOnStart { interactiveDrawModeUpdateDelayMillis = 16 // TODO: consider 33 for better battery life } @@ -321,8 +321,8 @@ class WatchCanvasRenderer( if (!watchState.isHeadless) { if (isAmbient) { - ambientExitAnimator.cancel() ambientEnterAnimator.cancel() + ambientExitAnimator.cancel() ambientEnterAnimator.setupStartValues() ambientEnterAnimator.start() From 5c3a28ec6de5582f03a94dbe3d7ae0604644dfc8 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Tue, 19 Mar 2024 18:20:12 +0200 Subject: [PATCH 32/52] wip --- .../main/java/dev/rdnt/m8face/BitmapCache.kt | 14 + .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 886 +++++++++++++----- 2 files changed, 652 insertions(+), 248 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt index e77508f..452762e 100644 --- a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt +++ b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt @@ -7,9 +7,19 @@ const val MINUTES_BITMAP_KEY = "minutes" const val SECONDS_BITMAP_KEY = "seconds" const val AMPM_BITMAP_KEY = "ampm" +const val TIME_BITMAP_KEY = "time" +const val AUX_BITMAP_KEY = "aux" + class BitmapCache { private val entries: MutableMap = mutableMapOf() + override fun toString(): String { + //loop entries + var result = "" + entries.forEach { (k, v) -> result += "{$k: $v} " } + return "(entries= $result)" + } + fun get(k: String, h: String): Bitmap? { return entries[k]?.get(h) } @@ -34,6 +44,10 @@ class BitmapCacheEntry { private var hash: String = "" private var bitmap: Bitmap? = null + override fun toString(): String { + return "(renders=$renders, loads=$loads)" + } + fun get(h: String): Bitmap? { loads++ diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index e867b46..bffd471 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -27,9 +27,11 @@ import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF import android.util.FloatProperty +import android.util.Log import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep +import androidx.core.animation.doOnCancel import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import androidx.core.graphics.ColorUtils @@ -64,6 +66,7 @@ import java.time.Duration import java.time.ZonedDateTime import kotlin.math.pow import kotlin.math.sqrt +import kotlin.system.measureNanoTime import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -192,7 +195,17 @@ class WatchCanvasRenderer( private val secondPaint = Paint().apply { isAntiAlias = true // make sure text is not anti-aliased even with this on typeface = context.resources.getFont(R.font.m8stealth57) - textSize = 112f / 14f + color = watchFaceColors.tertiaryColor + textSize = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> 56f + else -> 48f + } + } + + private val ampmPaint = Paint().apply { + isAntiAlias = true // make sure text is not anti-aliased even with this on + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 112f / 14f * 5f color = watchFaceColors.tertiaryColor } @@ -248,7 +261,7 @@ class WatchCanvasRenderer( private var debug: Boolean = watchFaceData.debug - private val ambientTransitionMs = 1000L + private val ambientTransitionMs = 750L private var drawProperties = DrawProperties() private var isHeadless = false private var isAmbient = false @@ -266,17 +279,32 @@ class WatchCanvasRenderer( drawProperties, DrawProperties.TIME_SCALE, 0f, 1.0f ).apply { duration = ambientTransitionMs -// setAutoCancel(true) + setAutoCancel(true) doOnStart { - interactiveDrawModeUpdateDelayMillis = 16 + Log.d("@@@", "AMBIENT -> INTERACTIVE START") +// interactiveDrawModeUpdateDelayMillis = 16 +// animating = true + } + doOnCancel { +// animationCanceled = true } doOnEnd { - interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay + if (!animationCanceled) { + Log.d("@@@", "AMBIENT -> INTERACTIVE END") + interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay + animating = false + animationCanceled = false + } else { + Log.d("@@@", "AMBIENT -> INTERACTIVE CANCEL") + } } }, ) } + private var animating: Boolean = false + private var animationCanceled: Boolean = false + private val ambientEnterAnimator = AnimatorSet().apply { interpolator = AnimationUtils.loadInterpolator( context, android.R.interpolator.accelerate_decelerate @@ -297,16 +325,30 @@ class WatchCanvasRenderer( duration = ambientTransitionMs // setAutoCancel(true) doOnStart { - interactiveDrawModeUpdateDelayMillis = 16 // TODO: consider 33 for better battery life +// interactiveDrawModeUpdateDelayMillis = 16 // TODO: consider 33 for better battery life +// animating = true + Log.d("@@@", "INTERACTIVE -> AMBIENT START") + } + doOnCancel { +// animationCanceled = true } doOnEnd { - interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay + if (!animationCanceled) { + interactiveDrawModeUpdateDelayMillis = 60000 + animating = false + Log.d("@@@", "INTERACTIVE -> AMBIENT END") + animationCanceled = false + } else { + Log.d("@@@", "INTERACTIVE -> AMBIENT CANCEL") + } } }, ) } init { + preloadBitmaps() + scope.launch { currentUserStyleRepository.userStyle.collect { userStyle -> updateWatchFaceData(userStyle) @@ -319,18 +361,28 @@ class WatchCanvasRenderer( isAmbient = ambient!! if (!watchState.isHeadless) { + animating = true if (isAmbient) { + animationCanceled = true ambientEnterAnimator.cancel() ambientExitAnimator.cancel() + interactiveDrawModeUpdateDelayMillis = 16 + + animationCanceled = false ambientEnterAnimator.setupStartValues() +// drawProperties.timeScale = 0f ambientEnterAnimator.start() } else { + animationCanceled = true ambientExitAnimator.cancel() ambientEnterAnimator.cancel() + interactiveDrawModeUpdateDelayMillis = 16 + + animationCanceled = false ambientExitAnimator.setupStartValues() ambientExitAnimator.start() } @@ -463,6 +515,12 @@ class WatchCanvasRenderer( ambientMinutePaint.color = watchFaceColors.secondaryColor secondPaint.color = watchFaceColors.tertiaryColor + secondPaint.textSize = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> 56f + else -> 48f + } + + ampmPaint.color = watchFaceColors.tertiaryColor batteryPaint.color = watchFaceColors.tertiaryColor batteryIconPaint.color = watchFaceColors.tertiaryColor @@ -543,6 +601,190 @@ class WatchCanvasRenderer( else -> {} } } + + preloadBitmaps() + } + } + + private fun preloadBitmaps() { + val canvas = Canvas() + preloadHourBitmaps(canvas) + preloadMinuteBitmaps(canvas) + preloadSecondBitmaps(canvas) + preloadAmPmBitmaps(canvas) + } + + private fun preloadHourBitmaps(canvas: Canvas) { + + val text14 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 144f + } + + val paint = Paint(text14).apply { + color = watchFaceColors.primaryColor + } + + for (i in 0..23) { + val text = i.toString().padStart(2, '0') + + var textBounds = Rect() + paint.getTextBounds(text, 0, text.length, textBounds) + textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + + val bmp = Bitmap.createBitmap( + textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 + ) + + canvas.setBitmap(bmp) + + canvas.drawText( + text, + 0f, + textBounds.height().toFloat(), + paint, + ) + + val cacheKey = "hour_$text" + + bitmapCache.set(cacheKey, "", bmp) + } + } + + private fun preloadMinuteBitmaps(canvas: Canvas) { + val text14 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 144f + } + + val paint = Paint(text14).apply { + color = watchFaceColors.secondaryColor + } + + for (i in 0..59) { + val text = i.toString().padStart(2, '0') + + var textBounds = Rect() + paint.getTextBounds(text, 0, text.length, textBounds) + textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + + val bmp = Bitmap.createBitmap( + textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 + ) + + canvas.setBitmap(bmp) + + canvas.drawText( + text, + 0f, + textBounds.height().toFloat(), + paint, + ) + + val cacheKey = "minute_$text" + + bitmapCache.set(cacheKey, "", bmp) + } + } + + private fun preloadSecondBitmaps(canvas: Canvas) { + val text14 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 56f + } + + val paint = Paint(text14).apply { + color = watchFaceColors.tertiaryColor + } + + for (i in 0..60) { + val text = if (i == 60) "M8" else i.toString().padStart(2, '0') + + var textBounds = Rect() + paint.getTextBounds(text, 0, text.length, textBounds) + textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + + val bmp = Bitmap.createBitmap( + textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 + ) + + canvas.setBitmap(bmp) + + canvas.drawText( + text, + 0f, + textBounds.height().toFloat(), + paint, + ) + + val cacheKey = "second_$text" + + bitmapCache.set(cacheKey, "", bmp) + } + } + + private fun preloadAmPmBitmaps(canvas: Canvas) { + val text14 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 40f + } + + val paint = Paint(text14).apply { + color = watchFaceColors.tertiaryColor + } + + + if (true) { + val text = "AM" + + var textBounds = Rect() + paint.getTextBounds(text, 0, text.length, textBounds) + textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + + val bmp = Bitmap.createBitmap( + textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 + ) + + canvas.setBitmap(bmp) + + canvas.drawText( + text, + 0f, + textBounds.height().toFloat(), + paint, + ) + + val cacheKey = "ampm_$text" + + bitmapCache.set(cacheKey, "", bmp) + } + if (true) { + val text = "PM" + + var textBounds = Rect() + paint.getTextBounds(text, 0, text.length, textBounds) + textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + + val bmp = Bitmap.createBitmap( + textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 + ) + + canvas.setBitmap(bmp) + + canvas.drawText( + text, + 0f, + textBounds.height().toFloat(), + paint, + ) + + val cacheKey = "ampm_$text" + + bitmapCache.set(cacheKey, "", bmp) } } @@ -780,9 +1022,11 @@ class WatchCanvasRenderer( get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { if (watchFaceData.detailedAmbient) { - 95f +// 95f + 94f } else { - 95f +// 95f + 94f // interpolate(129f, 95f) } } @@ -806,18 +1050,18 @@ class WatchCanvasRenderer( val ampmOffsetX: Float get() = if (watchFaceData.detailedAmbient) { - 84f +// 84f + 83f } else { - 84f +// 84f + 83f // interpolate(84f+34f, 84f) } val secondsTextSize: Float get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.INFO1.id -> 6f - LayoutStyle.INFO3.id -> 6f - LayoutStyle.SPORT.id -> 7f - else -> 0f + LayoutStyle.SPORT.id -> 56f + else -> 48f } // val secondsTextScale: Float @@ -870,237 +1114,289 @@ class WatchCanvasRenderer( zonedDateTime: ZonedDateTime, sharedAssets: AnalogSharedAssets, ) { - frame++ + val took = measureNanoTime { + frame++ - canvas.drawColor(Color.parseColor("#ff000000")) + canvas.drawColor(Color.parseColor("#ff000000")) - if (debug) { - val p2 = Paint() - p2.color = Color.parseColor("#aaff1111") - p2.typeface = context.resources.getFont(R.font.m8stealth57) - p2.textSize = 8f - - val text = "f $frame s ${"%.2f".format(drawProperties.timeScale)}" +// if (debug) { +// val p2 = Paint() +// p2.color = Color.parseColor("#aaff1111") +// p2.typeface = context.resources.getFont(R.font.m8stealth57) +// p2.textSize = 8f +// +// val text = "f $frame s ${"%.2f".format(drawProperties.timeScale)}" +// +// val textBounds = Rect() +// p2.getTextBounds(text, 0, text.length, textBounds) +// +// canvas.drawText( +// text, +// 192f - textBounds.width() / 2, +// 192f + textBounds.height() / 2, +// p2, +// ) +// } - val textBounds = Rect() - p2.getTextBounds(text, 0, text.length, textBounds) + var offsetX = + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( + 35f / 14f * 18f, + 0f + ) else 0f +// val offsetX = 0f - canvas.drawText( - text, - 192f - textBounds.width() / 2, - 192f + textBounds.height() / 2, - p2, - ) - } + val text14 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 144f + } - var offsetX = - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( - 35f/14f*18f, - 0f - ) else 0f -// val offsetX = 0f + val text16 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57thin) + textSize = 9f + } - val text14 = Paint(hourPaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57) - textSize = 144f - } + val text18 = Paint(hourPaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57thick) + textSize = 9f + } - val text16 = Paint(hourPaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57thin) - textSize = 9f - } + val hourText = when { + (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f) -> { + Paint(text16) + } - val text18 = Paint(hourPaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57thick) - textSize = 9f - } + (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f) -> { + Paint(text18) + } - val hourText = when { - (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f) -> { - Paint(text16) + else -> { + Paint(text14) + } } - (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f) -> { - Paint(text18) + + val minuteText14 = Paint(minutePaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 144f } - else -> { - Paint(text14) + + val minuteText16 = Paint(minutePaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57thin) + textSize = 9f } - } - val minuteText14 = Paint(minutePaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57) - textSize = 144f - } + val minuteText18 = Paint(minutePaint).apply { + isAntiAlias = false + typeface = context.resources.getFont(R.font.m8stealth57thick) + textSize = 9f + } - val minuteText16 = Paint(minutePaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57thin) - textSize = 9f - } - val minuteText18 = Paint(minutePaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57thick) - textSize = 9f - } + val minuteText = when { + (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f) -> { + Paint(minuteText16) + } + (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f) -> { + Paint(minuteText18) + } - val minuteText = when { - (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f) -> { - Paint(minuteText16) - } - (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f) -> { - Paint(minuteText18) - } - else -> { - Paint(minuteText14) + else -> { + Paint(minuteText14) + } } - } - canvas.withScale(timeTextScaleThick, timeTextScaleThick, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withScale( + timeTextScaleThick, + timeTextScaleThick, + bounds.exactCenterX(), + bounds.exactCenterY() + ) { canvas.withTranslation(offsetX, 0f) { - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - drawText( - canvas, - bounds, - getHour(zonedDateTime).toString().padStart(2, '0'), - hourText, - 1f, - timeOffsetXThick, - hourOffsetYThick, - 1f, - HOURS_BITMAP_KEY - ) + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - drawText( - canvas, - bounds, - zonedDateTime.minute.toString().padStart(2, '0'), - minuteText, - 1f, - timeOffsetXThick, - minuteOffsetYThick, - 1f, - MINUTES_BITMAP_KEY - ) + if (true) { + val hourText = getHour(zonedDateTime).toString().padStart(2, '0') + val cacheKey = "hour_$hourText" + + val hourBmp = bitmapCache.get(cacheKey, "")!! + + val hourOffsetX = timeOffsetXThick + val hourOffsetY = hourOffsetYThick + + canvas.drawBitmap( + hourBmp, + 192f - hourBmp.width / 2 + hourOffsetX, + 192f - hourBmp.height / 2 + hourOffsetY, + Paint(), + ) + } + + + if (true) { + val minuteText = zonedDateTime.minute.toString().padStart(2, '0') + val cacheKey = "minute_$minuteText" + + val minuteBmp = bitmapCache.get(cacheKey, "")!! + + val minuteOffsetX = timeOffsetXThick + val minuteOffsetY = minuteOffsetYThick + + canvas.drawBitmap( + minuteBmp, + 192f - minuteBmp.width / 2 + minuteOffsetX, + 192f - minuteBmp.height / 2 + minuteOffsetY, + Paint(), + ) + } + +// renderTime( +// canvas, +// bounds, +// zonedDateTime, +// hourText, +// minuteText, +// ) + + + +// drawText( +// canvas, +// getHour(zonedDateTime).toString().padStart(2, '0'), +// hourText, +// timeOffsetXThick, +// hourOffsetYThick, +// HOURS_BITMAP_KEY +// ) +// +// drawText( +// canvas, +// zonedDateTime.minute.toString().padStart(2, '0'), +// minuteText, +// timeOffsetXThick, +// minuteOffsetYThick, +// MINUTES_BITMAP_KEY +// ) + } } } - } - offsetX = - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( - 35f, - 0f - ) else 0f + offsetX = + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( + 35f, + 0f + ) else 0f - canvas.withScale(timeTextScale, timeTextScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.withTranslation(offsetX, 0f) { + canvas.withScale(timeTextScale, timeTextScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withTranslation(offsetX, 0f) { - val compBmp = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) + val compBmp = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) - val compCanvas = Canvas(compBmp) - - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { -// drawText( -// canvas, -// bounds, -// getHour(zonedDateTime).toString().padStart(2, '0'), -// hourPaint, -// 1f, -// timeOffsetX, -// hourOffsetY, -// timeTextSize, -// HOURS_BITMAP_KEY -// ) - - - -// drawText( -// canvas, -// bounds, -// zonedDateTime.minute.toString().padStart(2, '0'), -// minutePaint, -// 1f, -// timeOffsetX, -// minuteOffsetY, -// timeTextSize, -// MINUTES_BITMAP_KEY -// ) - - if (shouldDrawSeconds) { - drawText( - compCanvas, - bounds, - if (watchFaceData.detailedAmbient && isAmbient) "M8" else zonedDateTime.second.toString() - .padStart(2, '0'), - secondPaint, - 1f, - secondsOffsetX, - secondsOffsetY, - secondsTextSize, - SECONDS_BITMAP_KEY, - ) - } + val compCanvas = Canvas(compBmp) + + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { + if (shouldDrawSeconds) { + val secondText = if (watchFaceData.detailedAmbient && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') + val cacheKey = "second_$secondText" + + val secondBmp = bitmapCache.get(cacheKey, "")!! + + compCanvas.drawBitmap( + secondBmp, + 192f - secondBmp.width / 2 + secondsOffsetX, + 192f - secondBmp.height / 2 + secondsOffsetY, + Paint(), + ) - if (shouldDrawAmPm) { - drawText( - compCanvas, - bounds, - getAmPm(zonedDateTime).uppercase(), - secondPaint, - 1f, - ampmOffsetX, - 24f, - 5f, - AMPM_BITMAP_KEY, - ) - } - when (watchFaceData.secondsStyle.id) { - SecondsStyle.DASHES.id -> { - drawDashes(compCanvas, bounds, zonedDateTime) + + +// drawText( +// compCanvas, +// bounds, +// if (watchFaceData.detailedAmbient && isAmbient) "M8" else zonedDateTime.second.toString() +// .padStart(2, '0'), +// secondPaint, +// secondsOffsetX, +// secondsOffsetY, +// SECONDS_BITMAP_KEY, +// ) } - SecondsStyle.DOTS.id -> { - drawDots(compCanvas, bounds, zonedDateTime) + if (shouldDrawAmPm) { + val ampmText = getAmPm(zonedDateTime).uppercase() + val cacheKey = "ampm_$ampmText" + + val ampmBmp = bitmapCache.get(cacheKey, "")!! + + compCanvas.drawBitmap( + ampmBmp, + 192f - ampmBmp.width / 2 + ampmOffsetX, + 192f - ampmBmp.height / 2 + 24f, + Paint(), + ) + +// drawText( +// compCanvas, +// bounds, +// getAmPm(zonedDateTime).uppercase(), +// ampmPaint, +// ampmOffsetX, +// 24f, +// AMPM_BITMAP_KEY, +// ) } - } - } + when (watchFaceData.secondsStyle.id) { + SecondsStyle.DASHES.id -> { + drawDashes(compCanvas, bounds, zonedDateTime) + } - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) - ) { - drawComplications(compCanvas, zonedDateTime) - } + SecondsStyle.DOTS.id -> { + drawDots(compCanvas, bounds, zonedDateTime) + } + } + + } - val scale = if (isAmbient) .75f else 1f + if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && + (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) + ) { + drawComplications(compCanvas, zonedDateTime) + } + + val scale = if (isAmbient) .75f else 1f // val scale = interpolate(.75f, 1f) // TODO scaling opacity looks weird due to automatic brightness kicking in - val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) + val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) - canvas.drawBitmap( - compBmp, - bounds.left.toFloat(), - bounds.top.toFloat(), - Paint().apply { alpha = (opacity * 255).toInt() }, - ) + canvas.drawBitmap( + compBmp, + bounds.left.toFloat(), + bounds.top.toFloat(), + Paint().apply { alpha = (opacity * 255).toInt() }, + ) + } } + } + if (debug) { + Log.d("WatchCanvasRenderer", "render took ${took.toFloat() / 1000000.0}ms, $bitmapCache") + } } override fun shouldAnimate(): Boolean { - return super.shouldAnimate() || ambientEnterAnimator.isRunning || ambientExitAnimator.isRunning + return super.shouldAnimate() || animating } // ----- All drawing functions ----- @@ -1152,91 +1448,185 @@ class WatchCanvasRenderer( } } + private fun renderTime( + canvas: Canvas, + bounds: Rect, + zonedDateTime: ZonedDateTime, + hourPaint: Paint, + minutePaint: Paint, + ) { + val hourText = getHour(zonedDateTime).toString().padStart(2, '0') + val hourHash = "$hourText,${hourPaint.textSize},${hourPaint.color},$debug" + + val minuteText = zonedDateTime.minute.toString().padStart(2, '0') + val minuteHash = "$minuteText,${minutePaint.textSize},${minutePaint.color},$debug" + +// val timeHash = "$hourHash,$minuteHash" +// +// val cached = bitmapCache.get(TIME_BITMAP_KEY, timeHash) +// if (cached != null) { +// return cached +// } + +// val timeBmp = Bitmap.createBitmap( +// bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 +// ) +// +// val timeCanvas = Canvas(timeBmp) + + drawText2( + canvas, + bounds, + hourText, + hourPaint, + timeOffsetXThick, + hourOffsetYThick, + hourHash, + HOURS_BITMAP_KEY + ) + + + drawText2( + canvas, + bounds, + minuteText, + minutePaint, + timeOffsetXThick, + minuteOffsetYThick, + minuteHash, + MINUTES_BITMAP_KEY + ) + +// if (debug) { +// canvas.drawRect(bounds, Paint().apply { +// this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) +// style = Paint.Style.STROKE +// strokeWidth = 2f +// }) +// val p2 = Paint() +// p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) +// p2.typeface = context.resources.getFont(R.font.m8stealth57) +// p2.textSize = 8f +// canvas.drawText( +// "r ${bitmapCache.loads(TIME_BITMAP_KEY)}", +// 3f, +// bounds.height().toFloat()/2 - 7f, +// p2, +// ) +// +// canvas.drawText( +// "w ${bitmapCache.renders(TIME_BITMAP_KEY)}", +// 3f, +// bounds.height().toFloat()/2 + 8f, +// p2, +// ) +// } + +// bitmapCache.set(TIME_BITMAP_KEY, timeHash, timeBmp) + +// return canvas + } + private fun drawText( canvas: Canvas, bounds: Rect, text: String, paint: Paint, - opacity: Float, offsetX: Float, offsetY: Float, - textSize: Float, cacheKey: String, ) { - val bitmap = renderText(text, paint, textSize, paint.color, cacheKey) + val hash = "${text},${paint.textSize},${paint.color},${debug}" + + val bitmap = renderText(canvas, bounds, text, paint, cacheKey, hash) canvas.drawBitmap( bitmap, 192f - bitmap.width / 2 + offsetX, 192f - bitmap.height / 2 + offsetY, - Paint().apply { - alpha = (opacity * 255).toInt() - }, + Paint(), + ) + } + + private fun drawText2( + canvas: Canvas, + bounds: Rect, + text: String, + paint: Paint, + offsetX: Float, + offsetY: Float, + hash: String, + cacheKey: String, + ) { + val bitmap = renderText(canvas, bounds, text, paint, cacheKey, hash) + + canvas.drawBitmap( + bitmap, + 192f - bitmap.width / 2 + offsetX, + 192f - bitmap.height / 2 + offsetY, + Paint(), ) } private fun renderText( + canvas: Canvas, + bounds: Rect, text: String, paint: Paint, - textSize: Float, - color: Int, + cacheHash: String, cacheKey: String, ): Bitmap { - val hash = "${text},${textSize},${color},${debug}" - -// val cached = bitmapCache.get(cacheKey, hash) -// if (cached != null) { -// return cached +// if (debug) { +// canvas.drawRect(bounds, Paint().apply { +// this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) +// style = Paint.Style.STROKE +// strokeWidth = 2f +// }) +// val p2 = Paint() +// p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) +// p2.typeface = context.resources.getFont(R.font.m8stealth57) +// p2.textSize = 8f +// canvas.drawText( +// "r ${bitmapCache.loads(cacheKey)}", +// 3f, +// bounds.height().toFloat() - 12f, +// p2, +// ) +// +// canvas.drawText( +// "w ${bitmapCache.renders(cacheKey)}", +// 3f, +// bounds.height().toFloat() - 3f, +// p2, +// ) // } + val cached = bitmapCache.get(cacheKey, cacheHash) + if (cached != null) { + return cached + } + val p = Paint(paint) - p.textSize *= textSize - p.color = color - val textBounds = Rect() + var textBounds = Rect() p.getTextBounds(text, 0, text.length, textBounds) - val bounds = Rect(0, 0, textBounds.width(), textBounds.height()) + textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + val bmp = Bitmap.createBitmap( + textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 ) - val canvas = Canvas(bitmap) + val bmpCanvas = Canvas(bmp) - canvas.drawText( + bmpCanvas.drawText( text, 0f, - bounds.height().toFloat(), + textBounds.height().toFloat(), p, ) - if (debug) { - canvas.drawRect(bounds, Paint().apply { - this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) - style = Paint.Style.STROKE - strokeWidth = 2f - }) - val p2 = Paint() - p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) - p2.typeface = context.resources.getFont(R.font.m8stealth57) - p2.textSize = 8f - canvas.drawText( - "r ${bitmapCache.loads(cacheKey)}", - 3f, - bounds.height().toFloat() - 12f, - p2, - ) - - canvas.drawText( - "w ${bitmapCache.renders(cacheKey)}", - 3f, - bounds.height().toFloat() - 3f, - p2, - ) - } - - bitmapCache.set(cacheKey, hash, bitmap) + bitmapCache.set(cacheKey, cacheHash, bmp) - return bitmap + return bmp } private fun drawDashes( From 812372dc9d0c2044d45ce23fed0b5c711f2d96e6 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 24 Mar 2024 18:46:47 +0200 Subject: [PATCH 33/52] Before refactor to ambient style --- .../main/java/dev/rdnt/m8face/BitmapCache.kt | 17 +- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 969 ++++++++++++------ .../m8face/data/watchface/AmbientStyle.kt | 21 + .../m8face/data/watchface/WatchFaceData.kt | 2 - .../m8face/editor/WatchFaceConfigActivity.kt | 196 ++-- .../editor/WatchFaceConfigStateHolder.kt | 46 - .../m8face/utils/HorizontalComplication.kt | 140 +-- .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 122 ++- app/src/main/res/values/strings.xml | 3 + 9 files changed, 881 insertions(+), 635 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt index 452762e..eb2b9e9 100644 --- a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt +++ b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt @@ -1,14 +1,15 @@ package dev.rdnt.m8face import android.graphics.Bitmap - -const val HOURS_BITMAP_KEY = "hours" -const val MINUTES_BITMAP_KEY = "minutes" -const val SECONDS_BITMAP_KEY = "seconds" -const val AMPM_BITMAP_KEY = "ampm" - -const val TIME_BITMAP_KEY = "time" -const val AUX_BITMAP_KEY = "aux" +import android.util.Log + +//const val HOURS_BITMAP_KEY = "hours" +//const val MINUTES_BITMAP_KEY = "minutes" +//const val SECONDS_BITMAP_KEY = "seconds" +//const val AMPM_BITMAP_KEY = "ampm" +// +//const val TIME_BITMAP_KEY = "time" +//const val AUX_BITMAP_KEY = "aux" class BitmapCache { private val entries: MutableMap = mutableMapOf() diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index bffd471..82287f7 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -16,9 +16,7 @@ package dev.rdnt.m8face import android.animation.AnimatorSet -import android.animation.Keyframe import android.animation.ObjectAnimator -import android.animation.PropertyValuesHolder import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas @@ -31,9 +29,6 @@ import android.util.Log import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep -import androidx.core.animation.doOnCancel -import androidx.core.animation.doOnEnd -import androidx.core.animation.doOnStart import androidx.core.graphics.ColorUtils import androidx.core.graphics.withRotation import androidx.core.graphics.withScale @@ -52,10 +47,8 @@ import dev.rdnt.m8face.data.watchface.SecondsStyle import dev.rdnt.m8face.data.watchface.WatchFaceColorPalette.Companion.convertToWatchFaceColorPalette import dev.rdnt.m8face.data.watchface.WatchFaceData import dev.rdnt.m8face.utils.AMBIENT_STYLE_SETTING -import dev.rdnt.m8face.utils.BIG_AMBIENT_SETTING import dev.rdnt.m8face.utils.COLOR_STYLE_SETTING import dev.rdnt.m8face.utils.DEBUG_SETTING -import dev.rdnt.m8face.utils.DETAILED_AMBIENT_SETTING import dev.rdnt.m8face.utils.HorizontalComplication import dev.rdnt.m8face.utils.HorizontalTextComplication import dev.rdnt.m8face.utils.LAYOUT_STYLE_SETTING @@ -64,6 +57,7 @@ import dev.rdnt.m8face.utils.SECONDS_STYLE_SETTING import dev.rdnt.m8face.utils.VerticalComplication import java.time.Duration import java.time.ZonedDateTime +import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt import kotlin.system.measureNanoTime @@ -78,6 +72,7 @@ import kotlinx.coroutines.launch private const val DEFAULT_INTERACTIVE_DRAW_MODE_UPDATE_DELAY_MILLIS: Long = 16 + /** * Renders watch face via data in Room database. Also, updates watch face state based on setting * changes by user via [userStyleRepository.addUserStyleListener()]. @@ -103,6 +98,9 @@ class WatchCanvasRenderer( } } + // https://stackoverflow.com/a/32948752 + private val ambientTransitionMs = context.resources.getInteger(android.R.integer.config_longAnimTime).toLong() + private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main.immediate) @@ -116,7 +114,6 @@ class WatchCanvasRenderer( ambientStyle = AmbientStyle.OUTLINE, secondsStyle = SecondsStyle.NONE, militaryTime = true, - bigAmbient = false, layoutStyle = LayoutStyle.INFO1, debug = false, ) @@ -156,7 +153,8 @@ class WatchCanvasRenderer( private val ambientHourPaint = Paint().apply { isAntiAlias = true - val big = watchFaceData.bigAmbient +// val big = watchFaceData.bigAmbient + var big = false if (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id) { typeface = context.resources.getFont(R.font.m8stealth57thin) @@ -211,7 +209,8 @@ class WatchCanvasRenderer( private val ambientMinutePaint = Paint().apply { isAntiAlias = true - val big = watchFaceData.bigAmbient +// val big = watchFaceData.bigAmbient + var big = false if (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id) { typeface = context.resources.getFont(R.font.m8stealth57thin) @@ -261,97 +260,90 @@ class WatchCanvasRenderer( private var debug: Boolean = watchFaceData.debug - private val ambientTransitionMs = 750L - private var drawProperties = DrawProperties() + private var drawProperties = DrawProperties() // timeScale = 0f private var isHeadless = false private var isAmbient = false - private val bitmapCache: BitmapCache = BitmapCache() + private var bitmapCache: BitmapCache = BitmapCache() private var interactiveFrameDelay: Long = 16 - private val ambientExitAnimator = AnimatorSet().apply { - interpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.accelerate_decelerate - ) - play( - ObjectAnimator.ofFloat( - drawProperties, DrawProperties.TIME_SCALE, 0f, 1.0f - ).apply { - duration = ambientTransitionMs - setAutoCancel(true) - doOnStart { - Log.d("@@@", "AMBIENT -> INTERACTIVE START") -// interactiveDrawModeUpdateDelayMillis = 16 -// animating = true - } - doOnCancel { -// animationCanceled = true - } - doOnEnd { - if (!animationCanceled) { - Log.d("@@@", "AMBIENT -> INTERACTIVE END") - interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay - animating = false - animationCanceled = false - } else { - Log.d("@@@", "AMBIENT -> INTERACTIVE CANCEL") - } - } - }, - ) - } +// private var animating: Boolean = false - private var animating: Boolean = false - private var animationCanceled: Boolean = false + private var enteringAmbient = false + private var enteringInteractive = false +// private var enteringAmbientFrame = 0 +// private var enteringInteractiveFrame = 0 - private val ambientEnterAnimator = AnimatorSet().apply { - interpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.accelerate_decelerate - ) + private val ambientExitAnimator = + AnimatorSet().apply { + interpolator = + AnimationUtils.loadInterpolator( + context, + android.R.interpolator.accelerate_decelerate + ) + play( + ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1.0f).apply { + duration = ambientTransitionMs + setAutoCancel(true) + }, + ) + } - val keyframes = arrayOf( - Keyframe.ofFloat(0f, 1f), - Keyframe.ofFloat(0.9f, 0f), - Keyframe.ofFloat(1f, 0f), - ) + // Animation played when entering ambient mode. + private val ambientEnterAnimator = + AnimatorSet().apply { + interpolator = AnimationUtils.loadInterpolator( + context, + android.R.interpolator.accelerate_decelerate + ) - val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( - DrawProperties.TIME_SCALE, *keyframes - ) +// val keyframes = arrayOf( +// Keyframe.ofFloat(0f, 1f), +// Keyframe.ofFloat(1f, 0f), +// ) +// +// val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( +// DrawProperties.TIME_SCALE, *keyframes, Keyframe.ofFloat(1f, 0f) +// ) - play( - ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { - duration = ambientTransitionMs -// setAutoCancel(true) - doOnStart { -// interactiveDrawModeUpdateDelayMillis = 16 // TODO: consider 33 for better battery life -// animating = true - Log.d("@@@", "INTERACTIVE -> AMBIENT START") - } - doOnCancel { -// animationCanceled = true - } - doOnEnd { - if (!animationCanceled) { - interactiveDrawModeUpdateDelayMillis = 60000 - animating = false - Log.d("@@@", "INTERACTIVE -> AMBIENT END") - animationCanceled = false - } else { - Log.d("@@@", "INTERACTIVE -> AMBIENT CANCEL") - } - } - }, - ) + play( +// ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { +// duration = AMBIENT_TRANSITION_MS +// setAutoCancel(false) +// }, + ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 0.0f).apply { + duration = ambientTransitionMs + setAutoCancel(true) + }, + ) + } + +// override fun onRenderParametersChanged(renderParameters: RenderParameters) { +// super.onRenderParametersChanged(renderParameters) +// +// preloadBitmaps() +// } + + private var animating = false + + private lateinit var state: UserStyle + + var bitmapsInitialized: Boolean = false + var bitmapsScale: Float = 0f + + override suspend fun init() { + super.init() +// preloadBitmaps() } init { - preloadBitmaps() - scope.launch { currentUserStyleRepository.userStyle.collect { userStyle -> updateWatchFaceData(userStyle) + bitmapsInitialized = false +// preloadBitmaps(bounds.width()) + state = userStyle } } @@ -362,32 +354,103 @@ class WatchCanvasRenderer( if (!watchState.isHeadless) { animating = true + interactiveDrawModeUpdateDelayMillis = 16 + if (isAmbient) { - animationCanceled = true +// Log.d("@@@", "ambient on") +// drawProperties.timeScale = 0f +// ambientEnterAnimator.setupStartValues() - ambientEnterAnimator.cancel() + +// Log.d("@@@", "cancel before enter") +// ambientEnterAnimator.removeAllListeners() +// ambientExitAnimator.removeAllListeners() +// +// ambientEnterAnimator.cancel() +// ambientExitAnimator.cancel() +// +// interactiveDrawModeUpdateDelayMillis = 16 + +// ambientEnterAnimator.apply { +// doOnStart { +//// Log.d("@@@", "enter start") +//// interactiveDrawModeUpdateDelayMillis = 16 +// } +// +// doOnEnd { +//// Log.d("@@@", "enter end") +// +//// animating = false +//// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay +//// interactiveDrawModeUpdateDelayMillis = 16 +// } +// } ambientExitAnimator.cancel() - interactiveDrawModeUpdateDelayMillis = 16 +// enteringInteractive = false +// enteringAmbient = true +// enteringAmbientFrame = 1 +// interactiveDrawModeUpdateDelayMillis = 16 + +// if (!animating) { +// animating = true +// } + +// Log.d("@@@", ambientTransitionMs.toString()) - animationCanceled = false ambientEnterAnimator.setupStartValues() -// drawProperties.timeScale = 0f + ambientEnterAnimator.duration = (drawProperties.timeScale * ambientTransitionMs.toFloat()).toLong() + +// interactiveDrawModeUpdateDelayMillis = 16 ambientEnterAnimator.start() } else { - animationCanceled = true +// Log.d("@@@", "ambient off") +// interactiveDrawModeUpdateDelayMillis = 16 +// Log.d("@@@", "cancel before exit") + +// ambientEnterAnimator.removeAllListeners() +// ambientExitAnimator.removeAllListeners() +// +// ambientEnterAnimator.cancel() +// ambientExitAnimator.cancel() +// +// interactiveDrawModeUpdateDelayMillis = 16 +// animating = true + +// ambientExitAnimator.apply { +// doOnStart { +//// Log.d("@@@", "exit start") +//// animating = true +// } +// doOnEnd { +//// Log.d("@@@", "exit end") +//// animating = false +//// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay +// } +// } - ambientExitAnimator.cancel() ambientEnterAnimator.cancel() - interactiveDrawModeUpdateDelayMillis = 16 +// enteringInteractive = true +// enteringAmbient = false +// interactiveDrawModeUpdateDelayMillis = 16 +// enteringInteractiveFrame = 1 - animationCanceled = false +// if (!animating) { +// animating = true +// interactiveDrawModeUpdateDelayMillis = 16 +// } + +// Log.d("@@@", "starting from ${(1f-drawProperties.timeScale) * AMBIENT_TRANSITION_MS.toFloat()} / $AMBIENT_TRANSITION_MS") ambientExitAnimator.setupStartValues() +// ambientExitAnimator.currentPlayTime = ((1f-drawProperties.timeScale) * AMBIENT_TRANSITION_MS.toFloat()).toLong() + + ambientExitAnimator.duration = ((1f-drawProperties.timeScale) * ambientTransitionMs.toFloat()).toLong() +// interactiveDrawModeUpdateDelayMillis = 16 ambientExitAnimator.start() } } else { - drawProperties.timeScale = 0f + drawProperties.timeScale = 1f } } } @@ -469,22 +532,6 @@ class WatchCanvasRenderer( ) } - BIG_AMBIENT_SETTING -> { - val booleanValue = options.value as UserStyleSetting.BooleanUserStyleSetting.BooleanOption - - newWatchFaceData = newWatchFaceData.copy( - bigAmbient = booleanValue.value, - ) - } - - DETAILED_AMBIENT_SETTING -> { - val booleanValue = options.value as UserStyleSetting.BooleanUserStyleSetting.BooleanOption - - newWatchFaceData = newWatchFaceData.copy( - detailedAmbient = booleanValue.value, - ) - } - DEBUG_SETTING -> { val booleanValue = options.value as UserStyleSetting.BooleanUserStyleSetting.BooleanOption @@ -529,7 +576,11 @@ class WatchCanvasRenderer( ambientHourPaint.typeface = context.resources.getFont(R.font.m8stealth57thin) ambientHourPaint.textSize = 8F - if (watchFaceData.bigAmbient) { +// if (watchFaceData.bigAmbient) { +// ambientHourPaint.typeface = context.resources.getFont(R.font.m8stealth57thinbig) +// ambientHourPaint.textSize = 8F +// } + if (false) { ambientHourPaint.typeface = context.resources.getFont(R.font.m8stealth57thinbig) ambientHourPaint.textSize = 8F } @@ -537,41 +588,60 @@ class WatchCanvasRenderer( ambientMinutePaint.typeface = context.resources.getFont(R.font.m8stealth57thin) ambientMinutePaint.textSize = 8F - if (watchFaceData.bigAmbient) { +// if (watchFaceData.bigAmbient) { +// ambientMinutePaint.typeface = context.resources.getFont(R.font.m8stealth57thinbig) +// ambientMinutePaint.textSize = 8F +// } + if (false) { ambientMinutePaint.typeface = context.resources.getFont(R.font.m8stealth57thinbig) ambientMinutePaint.textSize = 8F } } else if (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id) { ambientHourPaint.typeface = context.resources.getFont(R.font.m8stealth57thick) ambientHourPaint.textSize = 8F - if (watchFaceData.bigAmbient) { +// if (watchFaceData.bigAmbient) { +// ambientHourPaint.typeface = context.resources.getFont(R.font.m8stealth57thickbig) +// ambientHourPaint.textSize = 8F +// } + if (false) { ambientHourPaint.typeface = context.resources.getFont(R.font.m8stealth57thickbig) ambientHourPaint.textSize = 8F } ambientMinutePaint.typeface = context.resources.getFont(R.font.m8stealth57thick) ambientMinutePaint.textSize = 8F - if (watchFaceData.bigAmbient) { +// if (watchFaceData.bigAmbient) { +// ambientMinutePaint.typeface = context.resources.getFont(R.font.m8stealth57thickbig) +// ambientMinutePaint.textSize = 8F +// } + if (false) { ambientMinutePaint.typeface = context.resources.getFont(R.font.m8stealth57thickbig) ambientMinutePaint.textSize = 8F } } else if (watchFaceData.ambientStyle.id == AmbientStyle.FILLED.id) { ambientHourPaint.typeface = context.resources.getFont(R.font.m8stealth57) ambientHourPaint.textSize = 112F / 14f * 16f - if (watchFaceData.bigAmbient) { +// if (watchFaceData.bigAmbient) { +// ambientHourPaint.textSize = 112F / 14f * 18f +// } + if (false) { ambientHourPaint.textSize = 112F / 14f * 18f } ambientMinutePaint.typeface = context.resources.getFont(R.font.m8stealth57) ambientMinutePaint.textSize = 112F / 14f * 16f - if (watchFaceData.bigAmbient) { +// if (watchFaceData.bigAmbient) { +// ambientMinutePaint.textSize = 112F / 14f * 18f +// } + if (false) { ambientMinutePaint.textSize = 112F / 14f * 18f } } is24Format = watchFaceData.militaryTime - debug = watchFaceData.debug +// debug = watchFaceData.debug + debug = false interactiveFrameDelay = when (watchFaceData.secondsStyle.id) { SecondsStyle.NONE.id -> if (shouldDrawSeconds) 1000 else 60000 @@ -601,189 +671,321 @@ class WatchCanvasRenderer( else -> {} } } - - preloadBitmaps() } } - private fun preloadBitmaps() { + private fun preloadBitmaps(scale: Float) { + Log.d("WatchCanvasRenderer", "preloadBitmaps($scale)") + bitmapsScale = scale + val canvas = Canvas() - preloadHourBitmaps(canvas) - preloadMinuteBitmaps(canvas) - preloadSecondBitmaps(canvas) - preloadAmPmBitmaps(canvas) + preloadHourBitmaps(canvas, scale) + preloadHourBitmapsOutline(canvas, scale) + preloadHourBitmapsOutlineBig(canvas, scale) + preloadHourBitmapsBoldOutline(canvas, scale) + preloadHourBitmapsBoldOutlineBig(canvas, scale) + preloadMinuteBitmaps(canvas, scale) + preloadMinuteBitmapsOutline(canvas, scale) + preloadMinuteBitmapsOutlineBig(canvas, scale) + preloadMinuteBitmapsBoldOutline(canvas, scale) + preloadMinuteBitmapsBoldOutlineBig(canvas, scale) + preloadSecondBitmaps(canvas, scale) + preloadAmPmBitmaps(canvas, scale) } - private fun preloadHourBitmaps(canvas: Canvas) { - - val text14 = Paint(hourPaint).apply { + private val hourPaintNormal2: Paint + get() = Paint(hourPaint).apply { isAntiAlias = false + + color = watchFaceColors.primaryColor + typeface = context.resources.getFont(R.font.m8stealth57) textSize = 144f } - val paint = Paint(text14).apply { + private val hourPaint2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false + color = watchFaceColors.primaryColor + + typeface = context.resources.getFont(R.font.m8stealth57thin) + textSize = 8f } - for (i in 0..23) { - val text = i.toString().padStart(2, '0') + private val hourPaintBig2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false - var textBounds = Rect() - paint.getTextBounds(text, 0, text.length, textBounds) - textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + color = watchFaceColors.primaryColor - val bmp = Bitmap.createBitmap( - textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 - ) + typeface = context.resources.getFont(R.font.m8stealth57thinbig) + textSize = 8f + } - canvas.setBitmap(bmp) + private val hourPaintBold2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false - canvas.drawText( - text, - 0f, - textBounds.height().toFloat(), - paint, - ) + color = watchFaceColors.primaryColor - val cacheKey = "hour_$text" + typeface = context.resources.getFont(R.font.m8stealth57thick) + textSize = 8f + } - bitmapCache.set(cacheKey, "", bmp) + private val hourPaintBoldBig2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false + + color = watchFaceColors.primaryColor + + typeface = context.resources.getFont(R.font.m8stealth57thickbig) + textSize = 8f } - } - private fun preloadMinuteBitmaps(canvas: Canvas) { - val text14 = Paint(hourPaint).apply { + private val minutePaintNormal2: Paint + get() = Paint(hourPaint).apply { isAntiAlias = false + + color = watchFaceColors.secondaryColor + typeface = context.resources.getFont(R.font.m8stealth57) textSize = 144f } - val paint = Paint(text14).apply { + private val minutePaint2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false + color = watchFaceColors.secondaryColor + + typeface = context.resources.getFont(R.font.m8stealth57thin) + textSize = 8f } - for (i in 0..59) { - val text = i.toString().padStart(2, '0') + private val minutePaintBig2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false - var textBounds = Rect() - paint.getTextBounds(text, 0, text.length, textBounds) - textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + color = watchFaceColors.secondaryColor - val bmp = Bitmap.createBitmap( - textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 - ) + typeface = context.resources.getFont(R.font.m8stealth57thinbig) + textSize = 8f + } - canvas.setBitmap(bmp) + private val minutePaintBold2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false - canvas.drawText( - text, - 0f, - textBounds.height().toFloat(), - paint, - ) + color = watchFaceColors.secondaryColor - val cacheKey = "minute_$text" + typeface = context.resources.getFont(R.font.m8stealth57thick) + textSize = 8f + } - bitmapCache.set(cacheKey, "", bmp) + private val minutePaintBoldBig2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false + + color = watchFaceColors.secondaryColor + + typeface = context.resources.getFont(R.font.m8stealth57thickbig) + textSize = 8f } - } - private fun preloadSecondBitmaps(canvas: Canvas) { - val text14 = Paint(hourPaint).apply { + private val secondPaintNormal2: Paint + get() = Paint(hourPaint).apply { isAntiAlias = false + + color = watchFaceColors.tertiaryColor + typeface = context.resources.getFont(R.font.m8stealth57) textSize = 56f } - val paint = Paint(text14).apply { + private val ampmPaint2: Paint + get() = Paint(hourPaint).apply { + isAntiAlias = false + color = watchFaceColors.tertiaryColor + + typeface = context.resources.getFont(R.font.m8stealth57) + textSize = 40f } - for (i in 0..60) { - val text = if (i == 60) "M8" else i.toString().padStart(2, '0') + private fun preloadBitmap(tmpCanvas: Canvas, time: String, paint: Paint): Bitmap { + var textBounds = Rect() + paint.getTextBounds(time, 0, time.length, textBounds) + textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) - var textBounds = Rect() - paint.getTextBounds(text, 0, text.length, textBounds) - textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + val bmp = Bitmap.createBitmap( + textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 + ) - val bmp = Bitmap.createBitmap( - textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 - ) + tmpCanvas.setBitmap(bmp) - canvas.setBitmap(bmp) + tmpCanvas.drawText( + time, + 0f, + textBounds.height().toFloat(), + paint, + ) - canvas.drawText( - text, - 0f, - textBounds.height().toFloat(), - paint, - ) + tmpCanvas.setBitmap(null) - val cacheKey = "second_$text" + return bmp + } + + private fun preloadHourBitmaps(canvas: Canvas, scale: Float) { + for (i in 0..23) { + val hour = i.toString().padStart(2, '0') + + val bmp = preloadBitmap(canvas, hour, hourPaintNormal2.apply { + textSize *= scale + }) + + val cacheKey = "hour_normal_$hour" bitmapCache.set(cacheKey, "", bmp) } } - private fun preloadAmPmBitmaps(canvas: Canvas) { - val text14 = Paint(hourPaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57) - textSize = 40f + private fun preloadHourBitmapsOutline(canvas: Canvas, scale: Float) { + for (i in 0..23) { + val hour = i.toString().padStart(2, '0') + + val bmp = preloadBitmap(canvas, hour, Paint(hourPaint2).apply { + textSize *= scale + }) + + val cacheKey = "hour_outline_$hour" + bitmapCache.set(cacheKey, "", bmp) } + } - val paint = Paint(text14).apply { - color = watchFaceColors.tertiaryColor + private fun preloadHourBitmapsOutlineBig(canvas: Canvas, scale: Float) { + for (i in 0..23) { + val hour = i.toString().padStart(2, '0') + + val bmp = preloadBitmap(canvas, hour, Paint(hourPaintBig2).apply { + textSize *= scale + }) + + val cacheKey = "hour_outline_big_$hour" + bitmapCache.set(cacheKey, "", bmp) } + } + private fun preloadHourBitmapsBoldOutline(canvas: Canvas, scale: Float) { + for (i in 0..23) { + val hour = i.toString().padStart(2, '0') - if (true) { - val text = "AM" + val bmp = preloadBitmap(canvas, hour, Paint(hourPaintBold2).apply { + textSize *= scale + }) - var textBounds = Rect() - paint.getTextBounds(text, 0, text.length, textBounds) - textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + val cacheKey = "hour_bold_outline_$hour" + bitmapCache.set(cacheKey, "", bmp) + } + } - val bmp = Bitmap.createBitmap( - textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 - ) + private fun preloadHourBitmapsBoldOutlineBig(canvas: Canvas, scale: Float) { + for (i in 0..23) { + val hour = i.toString().padStart(2, '0') - canvas.setBitmap(bmp) + val bmp = preloadBitmap(canvas, hour, Paint(hourPaintBoldBig2).apply { + textSize *= scale + }) - canvas.drawText( - text, - 0f, - textBounds.height().toFloat(), - paint, - ) + val cacheKey = "hour_bold_outline_big_$hour" + bitmapCache.set(cacheKey, "", bmp) + } + } - val cacheKey = "ampm_$text" + private fun preloadMinuteBitmaps(canvas: Canvas, scale: Float) { + for (i in 0..59) { + val minute = i.toString().padStart(2, '0') + + val bmp = preloadBitmap(canvas, minute, minutePaintNormal2.apply { + textSize *= scale + }) + + val cacheKey = "minute_normal_$minute" bitmapCache.set(cacheKey, "", bmp) } - if (true) { - val text = "PM" + } - var textBounds = Rect() - paint.getTextBounds(text, 0, text.length, textBounds) - textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) + private fun preloadMinuteBitmapsOutline(canvas: Canvas, scale: Float) { + for (i in 0..59) { + val minute = i.toString().padStart(2, '0') - val bmp = Bitmap.createBitmap( - textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 - ) + val bmp = preloadBitmap(canvas, minute, minutePaint2.apply { + textSize *= scale + }) - canvas.setBitmap(bmp) + val cacheKey = "minute_outline_$minute" + bitmapCache.set(cacheKey, "", bmp) + } + } - canvas.drawText( - text, - 0f, - textBounds.height().toFloat(), - paint, - ) + private fun preloadMinuteBitmapsOutlineBig(canvas: Canvas, scale: Float) { + for (i in 0..59) { + val minute = i.toString().padStart(2, '0') - val cacheKey = "ampm_$text" + val bmp = preloadBitmap(canvas, minute, minutePaintBig2.apply { + textSize *= scale + }) + val cacheKey = "minute_outline_big_$minute" + bitmapCache.set(cacheKey, "", bmp) + } + } + + private fun preloadMinuteBitmapsBoldOutline(canvas: Canvas, scale: Float) { + for (i in 0..59) { + val minute = i.toString().padStart(2, '0') + + val bmp = preloadBitmap(canvas, minute, minutePaintBold2.apply { + textSize *= scale + }) + + val cacheKey = "minute_bold_outline_$minute" + bitmapCache.set(cacheKey, "", bmp) + } + } + + private fun preloadMinuteBitmapsBoldOutlineBig(canvas: Canvas, scale: Float) { + for (i in 0..59) { + val minute = i.toString().padStart(2, '0') + + val bmp = preloadBitmap(canvas, minute, minutePaintBoldBig2.apply { + textSize *= scale + }) + + val cacheKey = "minute_bold_outline_big_$minute" + bitmapCache.set(cacheKey, "", bmp) + } + } + + private fun preloadSecondBitmaps(canvas: Canvas, scale: Float) { + for (i in 0..60) { + val second = if (i == 60) "M8" else i.toString().padStart(2, '0') + + val bmp = preloadBitmap(canvas, second, secondPaintNormal2.apply { + textSize *= scale + }) + val cacheKey = "second_normal_$second" + bitmapCache.set(cacheKey, "", bmp) + } + } + + private fun preloadAmPmBitmaps(canvas: Canvas, scale: Float) { + for (text in arrayOf("AM", "PM")) { + val bmp = preloadBitmap(canvas, text, ampmPaint2.apply { + textSize *= scale + }) + val cacheKey = "ampm_$text" bitmapCache.set(cacheKey, "", bmp) } } @@ -826,7 +1028,13 @@ class WatchCanvasRenderer( } else -> { - if (watchFaceData.detailedAmbient) { +// if (watchFaceData.detailedAmbient) { +// 14f +// } else { +//// interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) +// 14f +// } + if (false) { 14f } else { // interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) @@ -843,7 +1051,14 @@ class WatchCanvasRenderer( } else -> { - if (watchFaceData.detailedAmbient) { +// if (watchFaceData.detailedAmbient) { +//// 18f / 16f +// 0f +// } else { +//// interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) +// 18f / 16f +// } + if (false) { // 18f / 16f 0f } else { @@ -860,11 +1075,17 @@ class WatchCanvasRenderer( } else -> { - if (watchFaceData.detailedAmbient) { +// if (watchFaceData.detailedAmbient) { +// 14f / 14f +// } else { +// interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) +// } + if (false) { 14f / 14f } else { - interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) + interpolate(if (false) 18f / 14f else 16f / 14f, 14f / 14f) } + } } @@ -875,10 +1096,23 @@ class WatchCanvasRenderer( } else -> { - if (watchFaceData.detailedAmbient) { - 14f / 18f +// if (watchFaceData.detailedAmbient) { +// if (drawProperties.timeScale == 0f && watchFaceData.ambientStyle != AmbientStyle.FILLED) 16 / 18f else 14f / 18f +// } else { +// if (drawProperties.timeScale == 0f && watchFaceData.ambientStyle != AmbientStyle.FILLED) { +// 18f / 18f +// } else { +// interpolate(if (watchFaceData.bigAmbient) 18f / 18f else 16f / 18f, 14f / 18f) +// } +// } + if (false) { + if (drawProperties.timeScale == 0f && watchFaceData.ambientStyle != AmbientStyle.FILLED) 16 / 18f else 14f / 18f } else { - interpolate(if (watchFaceData.bigAmbient) 18f / 18f else 16f / 18f, 14f / 18f) + if (drawProperties.timeScale == 0f && watchFaceData.ambientStyle != AmbientStyle.FILLED) { + 18f / 18f + } else { + interpolate(if (false) 18f / 18f else 16f / 18f, 14f / 18f) + } } } } @@ -886,12 +1120,18 @@ class WatchCanvasRenderer( val timeOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { - if (watchFaceData.detailedAmbient) { + if (false) { -35f } else { -35f // interpolate(0f, -34f) } +// if (watchFaceData.detailedAmbient) { +// -35f +// } else { +// -35f +//// interpolate(0f, -34f) +// } } @@ -903,7 +1143,13 @@ class WatchCanvasRenderer( val timeOffsetXThick: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { - if (watchFaceData.detailedAmbient) { +// if (watchFaceData.detailedAmbient) { +// -35f/14f*18f +// } else { +// -35f/14f*18f +//// interpolate(0f, -34f) +// } + if (false) { -35f/14f*18f } else { -35f/14f*18f @@ -957,7 +1203,16 @@ class WatchCanvasRenderer( } else -> { - -72f +// if (drawProperties.timeScale == 0f && !watchFaceData.bigAmbient && watchFaceData.ambientStyle != AmbientStyle.FILLED) { +// -64f +// } else { +// -72f +// } + if (drawProperties.timeScale == 0f && !false && watchFaceData.ambientStyle != AmbientStyle.FILLED) { + -64f + } else { + -72f + } // 185f - 192f // -7f - 49f // interpolate(183f, 185f) @@ -999,7 +1254,16 @@ class WatchCanvasRenderer( } else -> { - 72f +// if (drawProperties.timeScale == 0f && !watchFaceData.bigAmbient && watchFaceData.ambientStyle != AmbientStyle.FILLED) { +// 64f +// } else { +// 72f +// } + if (drawProperties.timeScale == 0f && !false && watchFaceData.ambientStyle != AmbientStyle.FILLED) { + 64f + } else { + 72f + } // interpolate(327f, 297f) } } @@ -1021,7 +1285,15 @@ class WatchCanvasRenderer( val secondsOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { - if (watchFaceData.detailedAmbient) { +// if (watchFaceData.detailedAmbient) { +//// 95f +// 94f +// } else { +//// 95f +// 94f +//// interpolate(129f, 95f) +// } + if (false) { // 95f 94f } else { @@ -1049,14 +1321,25 @@ class WatchCanvasRenderer( } val ampmOffsetX: Float - get() = if (watchFaceData.detailedAmbient) { + get() = +// if (watchFaceData.detailedAmbient) { +//// 84f +// 83f +// } else { +//// 84f +// 83f +//// interpolate(84f+34f, 84f) +// } + if (false) { // 84f - 83f - } else { + 83f + } else { // 84f - 83f + 83f // interpolate(84f+34f, 84f) - } + } + + val ampmOffsetY: Float = 24f val secondsTextSize: Float get() = when (watchFaceData.layoutStyle.id) { @@ -1114,35 +1397,62 @@ class WatchCanvasRenderer( zonedDateTime: ZonedDateTime, sharedAssets: AnalogSharedAssets, ) { + // all calculations are done with 384x384 resolution in mind + val renderScale = min(bounds.width(), bounds.height()).toFloat() / 384.0f + + canvas.drawColor(Color.parseColor("#ff000000")) + + if (!this::state.isInitialized) { + // placeholder + return + } + + if (!bitmapsInitialized || bitmapsScale != renderScale) { + preloadBitmaps(renderScale) + bitmapsInitialized = true + } + val took = measureNanoTime { frame++ - canvas.drawColor(Color.parseColor("#ff000000")) + if (debug) { + val p2 = Paint() + p2.color = Color.parseColor("#aaff1111") + p2.typeface = context.resources.getFont(R.font.m8stealth57) + p2.textSize = 8f -// if (debug) { -// val p2 = Paint() -// p2.color = Color.parseColor("#aaff1111") -// p2.typeface = context.resources.getFont(R.font.m8stealth57) -// p2.textSize = 8f -// -// val text = "f $frame s ${"%.2f".format(drawProperties.timeScale)}" -// -// val textBounds = Rect() -// p2.getTextBounds(text, 0, text.length, textBounds) -// -// canvas.drawText( -// text, -// 192f - textBounds.width() / 2, -// 192f + textBounds.height() / 2, -// p2, -// ) -// } + val text = "f $frame r $interactiveDrawModeUpdateDelayMillis s ${"%.2f".format(drawProperties.timeScale)}" + + val textBounds = Rect() + p2.getTextBounds(text, 0, text.length, textBounds) + + canvas.drawText( + text, + 192f - textBounds.width() / 2, + 192f + textBounds.height() / 2, + p2, + ) + } var offsetX = - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( - 35f / 14f * 18f, - 0f - ) else 0f +// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( +// 35f / 14f * 18f, +// 0f +// ) else if (watchFaceData.detailedAmbient) { +// 0f +// } else { +// 0f +// +// } + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !false) interpolate( + 35f / 14f * 18f, + 0f + ) else if (false) { + 0f + } else { + 0f + + } // val offsetX = 0f val text14 = Paint(hourPaint).apply { @@ -1211,6 +1521,7 @@ class WatchCanvasRenderer( } canvas.withScale( +// 1f, 1f, timeTextScaleThick, timeTextScaleThick, bounds.exactCenterX(), @@ -1220,19 +1531,29 @@ class WatchCanvasRenderer( canvas.withTranslation(offsetX, 0f) { if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { + var textStyle = when { + watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f -> "bold_outline" + watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f -> "outline" + else -> "normal" + } + +// val size = if (watchFaceData.bigAmbient && textStyle != "normal") "_big" else "" + val size = if (false && textStyle != "normal") "_big" else "" + if (true) { val hourText = getHour(zonedDateTime).toString().padStart(2, '0') - val cacheKey = "hour_$hourText" + + val cacheKey = "hour_${textStyle}${size}_$hourText" val hourBmp = bitmapCache.get(cacheKey, "")!! - val hourOffsetX = timeOffsetXThick - val hourOffsetY = hourOffsetYThick + val hourOffsetX = timeOffsetXThick * renderScale + val hourOffsetY = hourOffsetYThick * renderScale canvas.drawBitmap( hourBmp, - 192f - hourBmp.width / 2 + hourOffsetX, - 192f - hourBmp.height / 2 + hourOffsetY, + bounds.exactCenterX() - hourBmp.width / 2f + hourOffsetX, + bounds.exactCenterY() - hourBmp.height / 2f + hourOffsetY, Paint(), ) } @@ -1240,17 +1561,17 @@ class WatchCanvasRenderer( if (true) { val minuteText = zonedDateTime.minute.toString().padStart(2, '0') - val cacheKey = "minute_$minuteText" + val cacheKey = "minute_${textStyle}${size}_$minuteText" val minuteBmp = bitmapCache.get(cacheKey, "")!! - val minuteOffsetX = timeOffsetXThick - val minuteOffsetY = minuteOffsetYThick + val minuteOffsetX = timeOffsetXThick * renderScale + val minuteOffsetY = minuteOffsetYThick * renderScale canvas.drawBitmap( minuteBmp, - 192f - minuteBmp.width / 2 + minuteOffsetX, - 192f - minuteBmp.height / 2 + minuteOffsetY, + bounds.exactCenterX() - minuteBmp.width / 2f + minuteOffsetX, + bounds.exactCenterY() - minuteBmp.height / 2f + minuteOffsetY, Paint(), ) } @@ -1287,10 +1608,14 @@ class WatchCanvasRenderer( } offsetX = - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( - 35f, - 0f - ) else 0f +// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( +// 35f, +// 0f +// ) else 0f + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !false) interpolate( + 35f, + 0f + ) else 0f @@ -1305,15 +1630,19 @@ class WatchCanvasRenderer( if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { if (shouldDrawSeconds) { - val secondText = if (watchFaceData.detailedAmbient && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') - val cacheKey = "second_$secondText" +// val secondText = if (watchFaceData.detailedAmbient && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') + val secondText = if (false && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') + val cacheKey = "second_normal_$secondText" val secondBmp = bitmapCache.get(cacheKey, "")!! + val offsetX = secondsOffsetX * renderScale + val offsetY = secondsOffsetY * renderScale + compCanvas.drawBitmap( secondBmp, - 192f - secondBmp.width / 2 + secondsOffsetX, - 192f - secondBmp.height / 2 + secondsOffsetY, + bounds.exactCenterX() - secondBmp.width / 2 + offsetX, + bounds.exactCenterY() - secondBmp.height / 2 + offsetY, Paint(), ) @@ -1338,10 +1667,13 @@ class WatchCanvasRenderer( val ampmBmp = bitmapCache.get(cacheKey, "")!! + val offsetX = ampmOffsetX * renderScale + val offsetY = ampmOffsetY * renderScale + compCanvas.drawBitmap( ampmBmp, - 192f - ampmBmp.width / 2 + ampmOffsetX, - 192f - ampmBmp.height / 2 + 24f, + bounds.exactCenterX() - ampmBmp.width / 2 + offsetX, + bounds.exactCenterY() - ampmBmp.height / 2 + offsetY, Paint(), ) @@ -1368,15 +1700,21 @@ class WatchCanvasRenderer( } +// if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && +// (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) +// ) { +// drawComplications(compCanvas, zonedDateTime) +// } if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) + (false || drawProperties.timeScale != 0f) ) { drawComplications(compCanvas, zonedDateTime) } val scale = if (isAmbient) .75f else 1f // val scale = interpolate(.75f, 1f) // TODO scaling opacity looks weird due to automatic brightness kicking in - val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) +// val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) + val opacity = if (false) scale else interpolate(0f, 1f) canvas.drawBitmap( compBmp, @@ -1393,10 +1731,54 @@ class WatchCanvasRenderer( if (debug) { Log.d("WatchCanvasRenderer", "render took ${took.toFloat() / 1000000.0}ms, $bitmapCache") } + + if (!(ambientExitAnimator.isRunning || ambientEnterAnimator.isRunning)) { + animating = false + } + +// if (ambientExitAnimator.isRunning) { +// if (!enteringInteractive) { +// enteringInteractive = true +// } +// } else if (enteringInteractive) { +// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay +// enteringInteractive = false +// } +// +// +// if (ambientEnterAnimator.isRunning) { +// if (!enteringAmbient) { +// enteringAmbient = true +// } +// } else if (enteringAmbient) { +//// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay +// enteringAmbient = false +// } + +// if (ambientEnterAnimator.isRunning || ambientExitAnimator.isRunning) { +//// if (!animating) { +//// animating = true +//// interactiveDrawModeUpdateDelayMillis = 16 +//// } +//// interactiveDrawModeUpdateDelayMillis = 16 +//// Log.d("@@@", "set to fast 16") +// } else { +// if (enteringInteractive) { +// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay +// enteringInteractive = false +// } +// +// if (enteringAmbient) { +// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay +// enteringAmbient = false +// } +//// Log.d("@@@", "set to int ${interactiveFrameDelay}") +// } + } override fun shouldAnimate(): Boolean { - return super.shouldAnimate() || animating + return super.shouldAnimate() || ambientEnterAnimator.isRunning || animating } // ----- All drawing functions ----- @@ -1482,7 +1864,8 @@ class WatchCanvasRenderer( timeOffsetXThick, hourOffsetYThick, hourHash, - HOURS_BITMAP_KEY + "", +// HOURS_BITMAP_KEY ) @@ -1494,7 +1877,8 @@ class WatchCanvasRenderer( timeOffsetXThick, minuteOffsetYThick, minuteHash, - MINUTES_BITMAP_KEY + "", +// MINUTES_BITMAP_KEY ) // if (debug) { @@ -1919,11 +2303,14 @@ class WatchCanvasRenderer( } private class DrawProperties( - var timeScale: Float = 0f + var timeScale: Float = 1f ) { companion object { val TIME_SCALE = object : FloatProperty("timeScale") { override fun setValue(obj: DrawProperties, value: Float) { + if (value.isNaN()) { + throw IllegalArgumentException("timeScale cannot be NaN") + } obj.timeScale = value } diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt index 956dabf..3058220 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt @@ -31,18 +31,36 @@ enum class AmbientStyle( OUTLINE( id = "outline", nameResourceId = R.string.outline_ambient_style_name, +// iconResourceId = R.drawable.outline_style_icon, + iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon + ), + BIG_OUTLINE( + id = "big_outline", + nameResourceId = R.string.big_outline_ambient_style_name, // TODO // iconResourceId = R.drawable.outline_style_icon, iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon ), BOLD_OUTLINE( id = "bold_outline", nameResourceId = R.string.bold_outline_ambient_style_name, +// iconResourceId = R.drawable.bold_outline_style_icon, + iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon + ), + BIG_BOLD_OUTLINE( + id = "big_bold_outline", + nameResourceId = R.string.big_bold_outline_ambient_style_name, // TODO // iconResourceId = R.drawable.bold_outline_style_icon, iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon ), FILLED( id = "filled", nameResourceId = R.string.filled_ambient_style_name, +// iconResourceId = R.drawable.filled_style_icon, + iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon + ), + DETAILED( + id = "detailed", + nameResourceId = R.string.detailed_ambient_style_name, // TODO // iconResourceId = R.drawable.filled_style_icon, iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon ); @@ -51,8 +69,11 @@ enum class AmbientStyle( fun getAmbientStyleConfig(id: String): AmbientStyle { return when (id) { OUTLINE.id -> OUTLINE + BIG_OUTLINE.id -> BIG_OUTLINE BOLD_OUTLINE.id -> BOLD_OUTLINE + BIG_BOLD_OUTLINE.id -> BIG_BOLD_OUTLINE FILLED.id -> FILLED + DETAILED.id -> DETAILED else -> OUTLINE } } diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt index 559e353..3165239 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt @@ -24,7 +24,5 @@ data class WatchFaceData( val ambientStyle: AmbientStyle = AmbientStyle.OUTLINE, val secondsStyle: SecondsStyle = SecondsStyle.NONE, val militaryTime: Boolean = true, - val bigAmbient: Boolean = true, - val detailedAmbient: Boolean = false, val debug: Boolean = false, ) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index 47c5c99..2336e14 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -347,8 +347,6 @@ fun WatchfaceConfigApp( val secondsStyleIndex = secondsStyles.indexOfFirst { it.id == state.userStylesAndPreview.secondsStyleId } val militaryTimeEnabled = state.userStylesAndPreview.militaryTime - val bigAmbientEnabled = state.userStylesAndPreview.bigAmbient - val detailedAmbientEnabled = state.userStylesAndPreview.detailedAmbient // val debugEnabled = state.userStylesAndPreview.debug Box( @@ -369,8 +367,6 @@ fun WatchfaceConfigApp( ambientStyleIndex, secondsStyleIndex, militaryTimeEnabled, - bigAmbientEnabled, - detailedAmbientEnabled, ) } } @@ -404,12 +400,10 @@ fun ConfigScaffold( ambientStyleIndex: Int, secondsStyleIndex: Int, militaryTimeEnabled: Boolean, - bigAmbientEnabled: Boolean, - detailedAmbientEnabled: Boolean, ) { Log.d( "Editor", - "ConfigScaffold($layoutIndex, $colorIndex, $ambientStyleIndex, $militaryTimeEnabled, $bigAmbientEnabled)" + "ConfigScaffold($layoutIndex, $colorIndex, $ambientStyleIndex, $militaryTimeEnabled)" ) val pagerState = rememberPagerState( @@ -464,7 +458,7 @@ fun ConfigScaffold( 2 -> SecondsStyleSelect(focusRequester2, stateHolder, secondsStyles, secondsStyleIndex) 3 -> AmbientStyleSelect(focusRequester3, stateHolder, ambientStyles, ambientStyleIndex) 4 -> ComplicationPicker(stateHolder, layoutIndex) - 5 -> Options(stateHolder, militaryTimeEnabled, bigAmbientEnabled, detailedAmbientEnabled) + 5 -> Options(stateHolder, militaryTimeEnabled) } } @@ -475,66 +469,67 @@ fun ConfigScaffold( ambientStyles.indexOfFirst { it.id == (stateHolder.uiState.value as WatchFaceConfigStateHolder.EditWatchFaceUiState.Success).userStylesAndPreview.ambientStyleId } - if (current == 0) { - if (!militaryTimeEnabled && !bigAmbientEnabled) { - id = R.drawable.preview_ambient_outline - } else if (militaryTimeEnabled && !bigAmbientEnabled) { - id = R.drawable.preview_ambient_outline_military - } else if (!militaryTimeEnabled && bigAmbientEnabled) { - id = R.drawable.preview_ambient_outline_big - } else if (militaryTimeEnabled && bigAmbientEnabled) { - id = R.drawable.preview_ambient_outline_military_big - } - } else if (current == 1) { - if (!militaryTimeEnabled && !bigAmbientEnabled) { - id = R.drawable.preview_ambient_bold - } else if (militaryTimeEnabled && !bigAmbientEnabled) { - id = R.drawable.preview_ambient_bold_military - } else if (!militaryTimeEnabled && bigAmbientEnabled) { - id = R.drawable.preview_ambient_bold_big - } else if (militaryTimeEnabled && bigAmbientEnabled) { - id = R.drawable.preview_ambient_bold_military_big - } - } else if (current == 2) { - if (!militaryTimeEnabled && !bigAmbientEnabled) { - id = R.drawable.preview_ambient_filled - } else if (militaryTimeEnabled && !bigAmbientEnabled) { - id = R.drawable.preview_ambient_filled_military - } else if (!militaryTimeEnabled && bigAmbientEnabled) { - id = R.drawable.preview_ambient_filled_big - } else if (militaryTimeEnabled && bigAmbientEnabled) { - id = R.drawable.preview_ambient_filled_military_big - } - } - - Image( - painterResource(id = id), - contentDescription = "Preview", - colorFilter = ColorFilter.tint( - colorResource(id = colorStyles[colorIndex].primaryColorId), - BlendMode.Darken - ), - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .zIndex(1f) - .clip(TopHalfRectShape) - .scale(1f) - ) - Image( - painterResource(id = id), - contentDescription = "Preview", - colorFilter = ColorFilter.tint( - colorResource(id = colorStyles[colorIndex].secondaryColorId), - BlendMode.Darken - ), - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .zIndex(1f) - .clip(BottomHalfRectShape) - .scale(1f) - ) + // TODO: @rdnt fix + Preview(bitmap) +// if (current == 0) { +// if (!militaryTimeEnabled && !bigAmbientEnabled) { +// id = R.drawable.preview_ambient_outline +// } else if (militaryTimeEnabled && !bigAmbientEnabled) { +// id = R.drawable.preview_ambient_outline_military +// } else if (!militaryTimeEnabled && bigAmbientEnabled) { +// id = R.drawable.preview_ambient_outline_big +// } else if (militaryTimeEnabled && bigAmbientEnabled) { +// id = R.drawable.preview_ambient_outline_military_big +// } +// } else if (current == 1) { +// if (!militaryTimeEnabled && !bigAmbientEnabled) { +// id = R.drawable.preview_ambient_bold +// } else if (militaryTimeEnabled && !bigAmbientEnabled) { +// id = R.drawable.preview_ambient_bold_military +// } else if (!militaryTimeEnabled && bigAmbientEnabled) { +// id = R.drawable.preview_ambient_bold_big +// } else if (militaryTimeEnabled && bigAmbientEnabled) { +// id = R.drawable.preview_ambient_bold_military_big +// } +// } else if (current == 2) { +// if (!militaryTimeEnabled && !bigAmbientEnabled) { +// id = R.drawable.preview_ambient_filled +// } else if (militaryTimeEnabled && !bigAmbientEnabled) { +// id = R.drawable.preview_ambient_filled_military +// } else if (!militaryTimeEnabled && bigAmbientEnabled) { +// id = R.drawable.preview_ambient_filled_big +// } else if (militaryTimeEnabled && bigAmbientEnabled) { +// id = R.drawable.preview_ambient_filled_military_big +// } +// } +// Image( +// painterResource(id = id), +// contentDescription = "Preview", +// colorFilter = ColorFilter.tint( +// colorResource(id = colorStyles[colorIndex].primaryColorId), +// BlendMode.Darken +// ), +// contentScale = ContentScale.Crop, +// modifier = Modifier +// .fillMaxSize() +// .zIndex(1f) +// .clip(TopHalfRectShape) +// .scale(1f) +// ) +// Image( +// painterResource(id = id), +// contentDescription = "Preview", +// colorFilter = ColorFilter.tint( +// colorResource(id = colorStyles[colorIndex].secondaryColorId), +// BlendMode.Darken +// ), +// contentScale = ContentScale.Crop, +// modifier = Modifier +// .fillMaxSize() +// .zIndex(1f) +// .clip(BottomHalfRectShape) +// .scale(1f) +// ) } else { Preview(bitmap) } @@ -676,8 +671,6 @@ fun Overlay( fun Options( stateHolder: WatchFaceConfigStateHolder, militaryTime: Boolean, - bigAmbient: Boolean, - detailedAmbient: Boolean, ) { Box( Modifier @@ -720,69 +713,6 @@ fun Options( }, ) - ToggleChip( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp, 0.dp) - .height((40.dp)), - checked = bigAmbient, - colors = ToggleChipDefaults.toggleChipColors( - checkedStartBackgroundColor = Transparent, - checkedEndBackgroundColor = Transparent, - uncheckedStartBackgroundColor = Transparent, - uncheckedEndBackgroundColor = Transparent, - ), - onCheckedChange = { - stateHolder.setBigAmbient(it) - }, - toggleControl = { - Switch( - modifier = Modifier.padding(0.dp), - checked = bigAmbient, - ) - }, - label = { - Text( - text = "Big Ambient", - fontSize = 12.sp, - fontWeight = Medium, - fontFamily = Default, - color = White - ) - }, - ) - - ToggleChip( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp, 0.dp) - .height((40.dp)), - checked = detailedAmbient, - colors = ToggleChipDefaults.toggleChipColors( - checkedStartBackgroundColor = Transparent, - checkedEndBackgroundColor = Transparent, - uncheckedStartBackgroundColor = Transparent, - uncheckedEndBackgroundColor = Transparent, - ), - onCheckedChange = { - stateHolder.setDetailedAmbient(it) - }, - toggleControl = { - Switch( - modifier = Modifier.padding(0.dp), - checked = detailedAmbient, - ) - }, - label = { - Text( - text = "Detailed Ambient", - fontSize = 12.sp, - fontWeight = Medium, - fontFamily = Default, - color = White - ) - }, - ) } } } diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index b5f747e..23d6fa0 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -122,14 +122,6 @@ class WatchFaceConfigStateHolder( militaryTimeKey = setting as UserStyleSetting.BooleanUserStyleSetting } - BIG_AMBIENT_SETTING -> { - bigAmbientKey = setting as UserStyleSetting.BooleanUserStyleSetting - } - - DETAILED_AMBIENT_SETTING -> { - detailedAmbientKey = setting as UserStyleSetting.BooleanUserStyleSetting - } - DEBUG_SETTING -> { debugKey = setting as UserStyleSetting.BooleanUserStyleSetting } @@ -175,12 +167,6 @@ class WatchFaceConfigStateHolder( val militaryTime = userStyle[militaryTimeKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption - val bigAmbient = - userStyle[bigAmbientKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption - - val detailedAmbient = - userStyle[detailedAmbientKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption - val debug = userStyle[debugKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption @@ -190,8 +176,6 @@ class WatchFaceConfigStateHolder( ambientStyleId = ambientStyle.id.toString(), secondsStyleId = secondsStyle.id.toString(), militaryTime = militaryTime.value, - bigAmbient = bigAmbient.value, - detailedAmbient = detailedAmbient.value, debug = debug.value, previewImage = bitmap, ) @@ -363,34 +347,6 @@ class WatchFaceConfigStateHolder( ) } - fun setBigAmbient(enabled: Boolean) { - setUserStyleOption( - bigAmbientKey, - UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(enabled) - ) - - if (enabled) { - setUserStyleOption( - detailedAmbientKey, - UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(false) - ) - } - } - - fun setDetailedAmbient(enabled: Boolean) { - setUserStyleOption( - detailedAmbientKey, - UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(enabled) - ) - - if (enabled) { - setUserStyleOption( - bigAmbientKey, - UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(false) - ) - } - } - fun setDebug(enabled: Boolean) { setUserStyleOption( debugKey, @@ -429,8 +385,6 @@ class WatchFaceConfigStateHolder( val ambientStyleId: String, val secondsStyleId: String, val militaryTime: Boolean, - val bigAmbient: Boolean, - val detailedAmbient: Boolean, val debug: Boolean, val previewImage: Bitmap, ) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index 8d39dad..352b1f0 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -9,7 +9,10 @@ import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.graphics.RectF +import android.graphics.drawable.Drawable +import android.os.Build import android.util.Log +import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap @@ -26,6 +29,7 @@ import dev.rdnt.m8face.BitmapCacheEntry import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime +import kotlin.math.max class HorizontalComplication(private val context: Context) : CanvasComplication { private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() @@ -100,37 +104,38 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) { if (bounds.isEmpty) return - val bitmap: Bitmap - - when (data.type) { + val bitmap = when (data.type) { ComplicationType.SHORT_TEXT -> { - bitmap = drawShortTextComplication(bounds, data as ShortTextComplicationData) + drawShortTextComplication(bounds, data as ShortTextComplicationData) + } + + ComplicationType.NO_DATA -> { + val placeholder = (data as NoDataComplicationData).placeholder + if (placeholder != null && placeholder.type == ComplicationType.SHORT_TEXT) { + drawShortTextComplication(bounds, placeholder as ShortTextComplicationData) + } else { + return + } } else -> return } -// canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { - renderDebug(canvas, bounds.toRectF()) + renderDebug(canvas, bounds.toRectF()) - canvas.drawBitmap( - bitmap, - bounds.left.toFloat(), - bounds.top.toFloat(), - Paint(), -// Paint().apply { alpha = (opacity * 255).toInt() }, - ) -// } + canvas.drawBitmap( + bitmap, + bounds.left.toFloat(), + bounds.top.toFloat(), + Paint(), + ) } - private fun drawShortTextComplication( bounds: Rect, data: ShortTextComplicationData ): Bitmap { - val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${debug}" - - val cached = bitmapCache.get(hash) + val cached = bitmapCache.get("") if (cached != null) { return cached } @@ -144,7 +149,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication renderShortTextComplication(bitmapCanvas, rect, data) - bitmapCache.set(hash, bitmap) + bitmapCache.set("", bitmap) return bitmap } @@ -177,78 +182,30 @@ class HorizontalComplication(private val context: Context) : CanvasComplication val now = Instant.now() var text = data.text.getTextAt(context.resources, now).toString().uppercase() - if (text == "--") { - return - } - - val isBattery = - data.dataSource?.className == "com.google.android.clockwork.sysui.experiences.complications.providers.BatteryProviderService" - val threeDigit = isBattery + val title = data.title?.getTextAt(context.resources, now)?.toString()?.uppercase() + if (title != null) { + text = "$text $title" + } - var title: String? = null var icon: Bitmap? = null var iconBounds = Rect() + if (title == null) { + val bmpSize = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.55f).toInt() - if (isBattery) { - val drawable = ContextCompat.getDrawable(context, R.drawable.battery_icon_32)!! - icon = drawable.toBitmap( - (32f / 186f * canvas.width).toInt(), - (32f / 186f * canvas.width).toInt() - ) - iconBounds = - Rect(0, 0, (32f / 186f * canvas.width).toInt(), (32f / 186f * canvas.width).toInt()) - } else if (data.monochromaticImage != null) { - val drawable = data.monochromaticImage!!.image.loadDrawable(context) - if (drawable != null) { - val size = (bounds.width().coerceAtMost(bounds.height()).toFloat() * 0.55f).toInt() - - icon = drawable.toBitmap(size, size) - iconBounds = Rect(0, 0, size, size) - } - } - - var prefixLen = 0 - - if (threeDigit) { - prefixLen = 3 - text.length - text = text.padStart(3, ' ') - } - - if (data.title != null && !data.title!!.isPlaceholder()) { - title = data.title!!.getTextAt(context.resources, now).toString().uppercase() + icon = data.monochromaticImage?.image?.loadDrawable(context)?.toBitmap(bmpSize, bmpSize) + iconBounds = Rect(0, 0, bmpSize, bmpSize) } textPaint.textSize = 24F / 186f * canvas.width val textBounds = Rect() - - if (threeDigit) { - textPaint.getTextBounds("000", 0, 3, textBounds) - } else { - textPaint.getTextBounds(text, 0, text.length, textBounds) - } - - val titleBounds = Rect() - - if (title != null) { - titlePaint.textSize = textPaint.textSize - titlePaint.getTextBounds(title, 0, title.length, titleBounds) - } + textPaint.getTextBounds(text, 0, text.length, textBounds) var iconOffsetX = 0f - var titleOffsetX = 0f var textOffsetX = 0f - if (title != null) { - val width = titleBounds.width() + textBounds.width() - - titleOffsetX = (width - titleBounds.width()).toFloat() / 2f - textOffsetX = (width - textBounds.width()).toFloat() / 2f - - titleOffsetX += 6f / 186f * canvas.width - textOffsetX += 6f / 186f * canvas.width - } else if (icon != null) { + if (icon != null) { val width = iconBounds.width() + textBounds.width() iconOffsetX = (width - iconBounds.width()).toFloat() / 2f @@ -256,20 +213,9 @@ class HorizontalComplication(private val context: Context) : CanvasComplication iconOffsetX += 9f / 186f * canvas.width textOffsetX += 9f / 186f * canvas.width - - if (isBattery) { - iconOffsetX = iconOffsetX.toInt().toFloat() - } } - if (title != null) { - canvas.drawText( - title, - bounds.exactCenterX() - titleBounds.width() / 2 - titleOffsetX, - bounds.exactCenterY() + titleBounds.height() / 2, - titlePaint - ) - } else if (icon != null) { + if (title == null && icon != null) { val dstRect = RectF( bounds.exactCenterX() - iconBounds.width() / 2f - iconOffsetX, bounds.exactCenterY() - iconBounds.height() / 2f, @@ -280,23 +226,10 @@ class HorizontalComplication(private val context: Context) : CanvasComplication canvas.drawBitmap(icon, iconBounds, dstRect, iconPaint) } - - if (prefixLen > 0) { - val prefix = "".padStart(prefixLen, '0') - prefixPaint.textSize = textPaint.textSize - - canvas.drawText( - prefix, - bounds.exactCenterX() - textBounds.width() / 2 + textOffsetX, - bounds.exactCenterY() + textBounds.height() / 2, - prefixPaint - ) - } - canvas.drawText( text, bounds.exactCenterX() - textBounds.width() / 2 + textOffsetX, - bounds.exactCenterY() + textBounds.height() / 2, + bounds.exactCenterY() + textPaint.fontSpacing / 2, textPaint ) } @@ -320,6 +253,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication loadDrawablesAsynchronous: Boolean ) { data = complicationData + bitmapCache.set("", null) } } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index fcbdc7d..8957f84 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -43,8 +43,6 @@ const val COLOR_STYLE_SETTING = "color_style_setting" const val AMBIENT_STYLE_SETTING = "ambient_style_setting" const val SECONDS_STYLE_SETTING = "seconds_style_setting" const val MILITARY_TIME_SETTING = "military_time_setting" -const val BIG_AMBIENT_SETTING = "big_ambient_setting" -const val DETAILED_AMBIENT_SETTING = "detailed_ambient_setting" const val DEBUG_SETTING = "debug_setting" /* @@ -55,31 +53,39 @@ const val DEBUG_SETTING = "debug_setting" fun createUserStyleSchema(context: Context): UserStyleSchema { // 1. Allows user to change the color styles of the watch face (if any are available). + // TODO: @rdnt fix icons, fix name resource IDs and screen reader name resource IDs + val info1 = ComplicationSlotsOption( id = UserStyleSetting.Option.Id(LayoutStyle.INFO1.id), resources = context.resources, displayNameResourceId = R.string.info1_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), +// screenReaderNameResourceId = R.string.sport_layout_style_name, complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( HOUR_COMPLICATION_ID, enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( MINUTE_COMPLICATION_ID, enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( LEFT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( TOP_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( BOTTOM_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ) ) @@ -88,31 +94,38 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.INFO2.id), resources = context.resources, displayNameResourceId = R.string.info2_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), +// screenReaderNameResourceId = R.string.sport_layout_style_name, complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( HOUR_COMPLICATION_ID, enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( MINUTE_COMPLICATION_ID, enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( LEFT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( RIGHT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( TOP_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( BOTTOM_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ) ) @@ -121,31 +134,38 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.INFO3.id), resources = context.resources, displayNameResourceId = R.string.info3_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), +// screenReaderNameResourceId = R.string.sport_layout_style_name, complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( HOUR_COMPLICATION_ID, enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( MINUTE_COMPLICATION_ID, enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( TOP_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( BOTTOM_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( COMPLICATIONS_TOP_LEFT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ) ) @@ -154,39 +174,48 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.INFO4.id), resources = context.resources, displayNameResourceId = R.string.info4_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), +// screenReaderNameResourceId = R.string.sport_layout_style_name, complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( HOUR_COMPLICATION_ID, enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( MINUTE_COMPLICATION_ID, enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( TOP_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( BOTTOM_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( COMPLICATIONS_TOP_LEFT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( COMPLICATIONS_BOTTOM_LEFT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( COMPLICATIONS_TOP_RIGHT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( COMPLICATIONS_BOTTOM_RIGHT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ) ) @@ -195,7 +224,8 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.SPORT.id), resources = context.resources, displayNameResourceId = R.string.sport_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), +// screenReaderNameResourceId = R.string.sport_layout_style_name, complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( HOUR_COMPLICATION_ID, @@ -206,6 +236,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { HOUR_SPORT_COMPLICATION_RIGHT_BOUND, HOUR_SPORT_COMPLICATION_BOTTOM_BOUND, )), +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( MINUTE_COMPLICATION_ID, @@ -216,18 +247,22 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { MINUTE_SPORT_COMPLICATION_RIGHT_BOUND, MINUTE_SPORT_COMPLICATION_BOTTOM_BOUND, )), +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( TOP_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( BOTTOM_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( RIGHT_TEXT_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ) ) @@ -236,7 +271,8 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { id = UserStyleSetting.Option.Id(LayoutStyle.FOCUS.id), resources = context.resources, displayNameResourceId = R.string.focus_layout_style_name, - icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), // TODO @rdnt fix icon + icon = Icon.createWithResource(context, R.drawable.aqua_style_icon), +// screenReaderNameResourceId = R.string.sport_layout_style_name, complicationSlotOverlays = listOf( ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( HOUR_COMPLICATION_ID, @@ -247,6 +283,7 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { HOUR_FOCUS_COMPLICATION_RIGHT_BOUND, HOUR_FOCUS_COMPLICATION_BOTTOM_BOUND, )), +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( MINUTE_COMPLICATION_ID, @@ -257,14 +294,17 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { MINUTE_FOCUS_COMPLICATION_RIGHT_BOUND, MINUTE_FOCUS_COMPLICATION_BOTTOM_BOUND, )), +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( FOCUS_LEFT_ICON_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay( FOCUS_RIGHT_ICON_COMPLICATION_ID, - enabled = true + enabled = true, +// nameResourceId = R.string.minute_complication_name, ), ) ) @@ -335,26 +375,6 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { DateFormat.is24HourFormat(context), // default ) - val bigAmbientSetting = UserStyleSetting.BooleanUserStyleSetting( - UserStyleSetting.Id(BIG_AMBIENT_SETTING), - context.resources, - R.string.big_ambient_setting, - R.string.big_ambient_setting_description, - null, - listOf(WatchFaceLayer.BASE), - false, - ) - - val detailedAmbientSetting = UserStyleSetting.BooleanUserStyleSetting( - UserStyleSetting.Id(DETAILED_AMBIENT_SETTING), - context.resources, - R.string.detailed_ambient_setting, - R.string.detailed_ambient_setting_description, - null, - listOf(WatchFaceLayer.BASE), - false, - ) - val debugSetting = UserStyleSetting.BooleanUserStyleSetting( UserStyleSetting.Id(DEBUG_SETTING), context.resources, @@ -373,8 +393,6 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { ambientStyleSetting, secondsStyleSetting, militaryTimeSetting, - bigAmbientSetting, - detailedAmbientSetting, debugSetting, ) ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0c5e10..e9f8906 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -78,8 +78,11 @@ Onyx Outline + Big Outline Bold + Big Bold Filled + Detailed None Dashes From 1898c974446810c53703689ac186cd660d5ad8ad Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 24 Mar 2024 23:16:51 +0200 Subject: [PATCH 34/52] Clean up offset/scaling rendering --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 326 ++++++------------ .../m8face/data/watchface/AmbientStyle.kt | 7 + app/src/main/res/values/strings.xml | 11 +- 3 files changed, 114 insertions(+), 230 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 82287f7..9025e23 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -280,7 +280,7 @@ class WatchCanvasRenderer( interpolator = AnimationUtils.loadInterpolator( context, - android.R.interpolator.accelerate_decelerate + android.R.interpolator.linear_out_slow_in ) play( ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1.0f).apply { @@ -295,7 +295,7 @@ class WatchCanvasRenderer( AnimatorSet().apply { interpolator = AnimationUtils.loadInterpolator( context, - android.R.interpolator.accelerate_decelerate + android.R.interpolator.fast_out_linear_in ) // val keyframes = arrayOf( @@ -871,7 +871,7 @@ class WatchCanvasRenderer( textSize *= scale }) - val cacheKey = "hour_outline_big_$hour" + val cacheKey = "hour_big_outline_$hour" bitmapCache.set(cacheKey, "", bmp) } } @@ -897,7 +897,7 @@ class WatchCanvasRenderer( textSize *= scale }) - val cacheKey = "hour_bold_outline_big_$hour" + val cacheKey = "hour_big_bold_outline_$hour" bitmapCache.set(cacheKey, "", bmp) } } @@ -937,7 +937,7 @@ class WatchCanvasRenderer( textSize *= scale }) - val cacheKey = "minute_outline_big_$minute" + val cacheKey = "minute_big_outline_$minute" bitmapCache.set(cacheKey, "", bmp) } } @@ -963,7 +963,7 @@ class WatchCanvasRenderer( textSize *= scale }) - val cacheKey = "minute_bold_outline_big_$minute" + val cacheKey = "minute_big_bold_outline_$minute" bitmapCache.set(cacheKey, "", bmp) } } @@ -1008,12 +1008,6 @@ class WatchCanvasRenderer( } } -// val scaleOffset = if (this.watchFaceData.bigAmbient) { -// 18f / 14f - 1f -// } else { -// 16f / 14f - 1f -// } - private val scale get() = if (!isHeadless) drawProperties.timeScale else 1f @@ -1021,53 +1015,6 @@ class WatchCanvasRenderer( return start + easeInOutCubic(scale) * (end - start) } - val timeTextSize: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 18f - } - - else -> { -// if (watchFaceData.detailedAmbient) { -// 14f -// } else { -//// interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) -// 14f -// } - if (false) { - 14f - } else { -// interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) - 14f - } - } - } - - val timeTextSizeThick: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { -// 22f / 16f - 0f - } - - else -> { -// if (watchFaceData.detailedAmbient) { -//// 18f / 16f -// 0f -// } else { -//// interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) -// 18f / 16f -// } - if (false) { -// 18f / 16f - 0f - } else { -// interpolate(if (watchFaceData.bigAmbient) 18f else 16f, 14f) - 18f / 16f - } - } - } - val timeTextScale: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.FOCUS.id -> { @@ -1080,10 +1027,10 @@ class WatchCanvasRenderer( // } else { // interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) // } - if (false) { + if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) { 14f / 14f } else { - interpolate(if (false) 18f / 14f else 16f / 14f, 14f / 14f) + interpolate(if (watchFaceData.ambientStyle == AmbientStyle.BIG_OUTLINE || watchFaceData.ambientStyle == AmbientStyle.BIG_BOLD_OUTLINE || watchFaceData.ambientStyle == AmbientStyle.BIG_FILLED) 18f / 14f else 16f / 14f, 14f / 14f) } } @@ -1091,183 +1038,87 @@ class WatchCanvasRenderer( val timeTextScaleThick: Float get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 18f / 18f - } - - else -> { -// if (watchFaceData.detailedAmbient) { -// if (drawProperties.timeScale == 0f && watchFaceData.ambientStyle != AmbientStyle.FILLED) 16 / 18f else 14f / 18f -// } else { -// if (drawProperties.timeScale == 0f && watchFaceData.ambientStyle != AmbientStyle.FILLED) { -// 18f / 18f -// } else { -// interpolate(if (watchFaceData.bigAmbient) 18f / 18f else 16f / 18f, 14f / 18f) -// } -// } - if (false) { - if (drawProperties.timeScale == 0f && watchFaceData.ambientStyle != AmbientStyle.FILLED) 16 / 18f else 14f / 18f - } else { - if (drawProperties.timeScale == 0f && watchFaceData.ambientStyle != AmbientStyle.FILLED) { - 18f / 18f - } else { - interpolate(if (false) 18f / 18f else 16f / 18f, 14f / 18f) + LayoutStyle.INFO1.id, LayoutStyle.INFO2.id, LayoutStyle.INFO3.id, LayoutStyle.INFO4.id, LayoutStyle.SPORT.id -> { + when(watchFaceData.ambientStyle) { + AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { + if (drawProperties.timeScale == 0f) { + 18f / 18f + } else { + interpolate(16f / 18f, 14f / 18f) + } } + AmbientStyle.BIG_OUTLINE, AmbientStyle.BIG_BOLD_OUTLINE, AmbientStyle.BIG_FILLED -> { + if (drawProperties.timeScale == 0f) { + 18f / 18f + } else { + interpolate(18f / 18f, 14f / 18f) + } + } + AmbientStyle.FILLED -> { + if (drawProperties.timeScale == 0f) { + 16f / 18f + } else { + interpolate(16f / 18f, 14f / 18f) + } + } + AmbientStyle.DETAILED -> 14f / 18f } } - } - val timeOffsetX: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> { - if (false) { - -35f - } else { - -35f -// interpolate(0f, -34f) + LayoutStyle.FOCUS.id -> { + when(watchFaceData.ambientStyle) { + AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { + if (drawProperties.timeScale == 0f) { + 18f / 18f + } else { + 16f / 18f + } + } + AmbientStyle.BIG_OUTLINE, AmbientStyle.BIG_BOLD_OUTLINE, AmbientStyle.BIG_FILLED -> { + if (drawProperties.timeScale == 0f) { + 18f / 18f + } else { + interpolate(18f / 18f, 16f / 18f) + } + } + AmbientStyle.FILLED-> 16f / 18f + AmbientStyle.DETAILED-> 16f / 18f } -// if (watchFaceData.detailedAmbient) { -// -35f -// } else { -// -35f -//// interpolate(0f, -34f) -// } - } - else -> { - 0f - } + else -> 18f / 18f } val timeOffsetXThick: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> { -// if (watchFaceData.detailedAmbient) { -// -35f/14f*18f -// } else { -// -35f/14f*18f -//// interpolate(0f, -34f) -// } - if (false) { - -35f/14f*18f - } else { - -35f/14f*18f -// interpolate(0f, -34f) - } - - } - - else -> { - 0f - } - } - -// val minutesOffsetX: Float -// get() = when (watchFaceData.layoutStyle.id) { -// LayoutStyle.SPORT.id -> { -// if (watchFaceData.detailedAmbient) { -// -34f - 11f -// } else { -// interpolate(0f, -34f - 11f) -// } -// } -// -// else -> { -// 0f -// } -// } - - val hourOffsetY: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { -// 183f - 192f - -72f -// -7f - 49f - } - - else -> { - -56f -// 185f - 192f -// -7f - 49f -// interpolate(183f, 185f) - } - } + get() = 0f val hourOffsetYThick: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { -// 183f - 192f - -72f -// -7f - 49f - } - - else -> { -// if (drawProperties.timeScale == 0f && !watchFaceData.bigAmbient && watchFaceData.ambientStyle != AmbientStyle.FILLED) { -// -64f -// } else { -// -72f -// } - if (drawProperties.timeScale == 0f && !false && watchFaceData.ambientStyle != AmbientStyle.FILLED) { + get() = when (watchFaceData.ambientStyle) { + AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { + if (drawProperties.timeScale == 0f) { -64f } else { -72f } -// 185f - 192f -// -7f - 49f -// interpolate(183f, 185f) } - } -// val minuteOffsetX: Float -// get() = when (watchFaceData.layoutStyle.id) { -// LayoutStyle.SPORT.id -> { -// 81f -// } -// -// LayoutStyle.FOCUS.id -> { -// 93f; -// } -// -// else -> { -//// 115f; -// interpolate(93f, 115f) -// } -// } - - val minuteOffsetY: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 58f + 10f + 4f - } - - else -> { - 56f -// interpolate(327f, 297f) - } + else -> -72f } val minuteOffsetYThick: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 72f - } - - else -> { -// if (drawProperties.timeScale == 0f && !watchFaceData.bigAmbient && watchFaceData.ambientStyle != AmbientStyle.FILLED) { -// 64f -// } else { -// 72f -// } - if (drawProperties.timeScale == 0f && !false && watchFaceData.ambientStyle != AmbientStyle.FILLED) { + get() = when (watchFaceData.ambientStyle) { + AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { + if (drawProperties.timeScale == 0f) { 64f } else { 72f } -// interpolate(327f, 297f) } + + else -> 72f } + val shouldDrawSeconds: Boolean get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.INFO1.id -> true @@ -1347,6 +1198,17 @@ class WatchCanvasRenderer( else -> 48f } + val offsetX2: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + when(watchFaceData.ambientStyle) { + AmbientStyle.DETAILED -> -45f + else -> interpolate(0f, -45f) + } + } + else -> 0f + } + // val secondsTextScale: Float // get() = when (watchFaceData.layoutStyle.id) { // LayoutStyle.FOCUS.id -> { @@ -1434,7 +1296,7 @@ class WatchCanvasRenderer( ) } - var offsetX = + var offsetXOld = // if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( // 35f / 14f * 18f, // 0f @@ -1444,10 +1306,13 @@ class WatchCanvasRenderer( // 0f // // } - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !false) interpolate( +// if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) { +// 0f +// } + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && watchFaceData.ambientStyle != AmbientStyle.DETAILED) interpolate( 35f / 14f * 18f, 0f - ) else if (false) { + ) else if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) { 0f } else { 0f @@ -1528,22 +1393,29 @@ class WatchCanvasRenderer( bounds.exactCenterY() ) { - canvas.withTranslation(offsetX, 0f) { + canvas.withTranslation(offsetX2, 0f) { if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { + val opacity = interpolate(0.75f, 1f) + var textStyle = when { - watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f -> "bold_outline" watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f -> "outline" + watchFaceData.ambientStyle.id == AmbientStyle.BIG_OUTLINE.id && drawProperties.timeScale == 0f -> "big_outline" + watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f -> "bold_outline" + watchFaceData.ambientStyle.id == AmbientStyle.BIG_BOLD_OUTLINE.id && drawProperties.timeScale == 0f -> "big_bold_outline" else -> "normal" } // val size = if (watchFaceData.bigAmbient && textStyle != "normal") "_big" else "" - val size = if (false && textStyle != "normal") "_big" else "" +// val size = if ( +// (drawProperties.timeScale == 0f) && (drawProperties.timeScale != 0f) && +// (watchFaceData.ambientStyle.id == AmbientStyle.BIG_OUTLINE.id || watchFaceData.ambientStyle.id == AmbientStyle.BIG_BOLD_OUTLINE.id)) +// "_big" else "" if (true) { val hourText = getHour(zonedDateTime).toString().padStart(2, '0') - val cacheKey = "hour_${textStyle}${size}_$hourText" + val cacheKey = "hour_${textStyle}_$hourText" val hourBmp = bitmapCache.get(cacheKey, "")!! @@ -1554,14 +1426,14 @@ class WatchCanvasRenderer( hourBmp, bounds.exactCenterX() - hourBmp.width / 2f + hourOffsetX, bounds.exactCenterY() - hourBmp.height / 2f + hourOffsetY, - Paint(), + Paint().apply { alpha = (opacity * 255).toInt() }, ) } if (true) { val minuteText = zonedDateTime.minute.toString().padStart(2, '0') - val cacheKey = "minute_${textStyle}${size}_$minuteText" + val cacheKey = "minute_${textStyle}_$minuteText" val minuteBmp = bitmapCache.get(cacheKey, "")!! @@ -1572,7 +1444,7 @@ class WatchCanvasRenderer( minuteBmp, bounds.exactCenterX() - minuteBmp.width / 2f + minuteOffsetX, bounds.exactCenterY() - minuteBmp.height / 2f + minuteOffsetY, - Paint(), + Paint().apply { alpha = (opacity * 255).toInt() }, ) } @@ -1607,12 +1479,12 @@ class WatchCanvasRenderer( } } - offsetX = + val offsetX = // if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( // 35f, // 0f // ) else 0f - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !false) interpolate( + if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && watchFaceData.ambientStyle != AmbientStyle.DETAILED) interpolate( 35f, 0f ) else 0f @@ -1706,15 +1578,19 @@ class WatchCanvasRenderer( // drawComplications(compCanvas, zonedDateTime) // } if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - (false || drawProperties.timeScale != 0f) + (watchFaceData.ambientStyle == AmbientStyle.DETAILED || drawProperties.timeScale != 0f) ) { drawComplications(compCanvas, zonedDateTime) } - val scale = if (isAmbient) .75f else 1f +// val scale = if (isAmbient) .75f else 1f // val scale = interpolate(.75f, 1f) // TODO scaling opacity looks weird due to automatic brightness kicking in // val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) - val opacity = if (false) scale else interpolate(0f, 1f) +// val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) scale else interpolate(0f, 1f) + + val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(0.75f, 1f) else interpolate(0f, 1f) + + canvas.drawBitmap( compBmp, diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt index 3058220..011ac68 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/AmbientStyle.kt @@ -55,6 +55,12 @@ enum class AmbientStyle( FILLED( id = "filled", nameResourceId = R.string.filled_ambient_style_name, +// iconResourceId = R.drawable.filled_style_icon, + iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon + ), + BIG_FILLED( + id = "big_filled", + nameResourceId = R.string.big_filled_ambient_style_name, // iconResourceId = R.drawable.filled_style_icon, iconResourceId = R.drawable.mauve_style_icon, // TODO @rdnt fix icon ), @@ -73,6 +79,7 @@ enum class AmbientStyle( BOLD_OUTLINE.id -> BOLD_OUTLINE BIG_BOLD_OUTLINE.id -> BIG_BOLD_OUTLINE FILLED.id -> FILLED + BIG_FILLED.id -> BIG_FILLED DETAILED.id -> DETAILED else -> OUTLINE } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e9f8906..3b2f246 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,11 +77,12 @@ Snow Onyx - Outline - Big Outline - Bold - Big Bold - Filled + Outline I + Outline II + Bold I + Bold II + Filled I + Filled II Detailed None From dd56c5da8b2f49880cd99c40bfd1313b0747c579 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 24 Mar 2024 23:39:56 +0200 Subject: [PATCH 35/52] Clean up complications scale --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 196 ++++++------------ 1 file changed, 63 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 9025e23..38b01ed 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -1015,28 +1015,7 @@ class WatchCanvasRenderer( return start + easeInOutCubic(scale) * (end - start) } - val timeTextScale: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.FOCUS.id -> { - 18f / 18f - } - - else -> { -// if (watchFaceData.detailedAmbient) { -// 14f / 14f -// } else { -// interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) -// } - if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) { - 14f / 14f - } else { - interpolate(if (watchFaceData.ambientStyle == AmbientStyle.BIG_OUTLINE || watchFaceData.ambientStyle == AmbientStyle.BIG_BOLD_OUTLINE || watchFaceData.ambientStyle == AmbientStyle.BIG_FILLED) 18f / 14f else 16f / 14f, 14f / 14f) - } - - } - } - - val timeTextScaleThick: Float + val timeScale: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.INFO1.id, LayoutStyle.INFO2.id, LayoutStyle.INFO3.id, LayoutStyle.INFO4.id, LayoutStyle.SPORT.id -> { when(watchFaceData.ambientStyle) { @@ -1089,10 +1068,28 @@ class WatchCanvasRenderer( else -> 18f / 18f } - val timeOffsetXThick: Float - get() = 0f + val complicationsScale: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.FOCUS.id -> { + when(watchFaceData.ambientStyle) { + AmbientStyle.BIG_OUTLINE, AmbientStyle.BIG_BOLD_OUTLINE, AmbientStyle.BIG_FILLED -> { + interpolate(18f / 16f, 16f / 16f) + } - val hourOffsetYThick: Float + else -> 16f / 16f + } + } + + else -> { + if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) { + 14f / 14f + } else { + interpolate(if (watchFaceData.ambientStyle == AmbientStyle.BIG_OUTLINE || watchFaceData.ambientStyle == AmbientStyle.BIG_BOLD_OUTLINE || watchFaceData.ambientStyle == AmbientStyle.BIG_FILLED) 18f / 14f else 16f / 14f, 14f / 14f) + } + } + } + + val hourOffsetY: Float get() = when (watchFaceData.ambientStyle) { AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { if (drawProperties.timeScale == 0f) { @@ -1105,7 +1102,7 @@ class WatchCanvasRenderer( else -> -72f } - val minuteOffsetYThick: Float + val minuteOffsetY: Float get() = when (watchFaceData.ambientStyle) { AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { if (drawProperties.timeScale == 0f) { @@ -1118,45 +1115,16 @@ class WatchCanvasRenderer( else -> 72f } - val shouldDrawSeconds: Boolean get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.INFO1.id -> true - LayoutStyle.INFO3.id -> true - LayoutStyle.SPORT.id -> true + LayoutStyle.INFO1.id, LayoutStyle.INFO3.id, LayoutStyle.SPORT.id -> true else -> false } -// val secondsOffsetX: Float -// get() = when(watchFaceData.layoutStyle.id) { -// LayoutStyle.SPORT.id -> 95f -// else -> 0f -// } - val secondsOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> { -// if (watchFaceData.detailedAmbient) { -//// 95f -// 94f -// } else { -//// 95f -// 94f -//// interpolate(129f, 95f) -// } - if (false) { -// 95f - 94f - } else { -// 95f - 94f -// interpolate(129f, 95f) - } - } - - else -> { - 129f - } + LayoutStyle.SPORT.id -> 94f + else -> 129f } val secondsOffsetY: Float @@ -1171,34 +1139,7 @@ class WatchCanvasRenderer( else -> false } - val ampmOffsetX: Float - get() = -// if (watchFaceData.detailedAmbient) { -//// 84f -// 83f -// } else { -//// 84f -// 83f -//// interpolate(84f+34f, 84f) -// } - if (false) { -// 84f - 83f - } else { -// 84f - 83f -// interpolate(84f+34f, 84f) - } - - val ampmOffsetY: Float = 24f - - val secondsTextSize: Float - get() = when (watchFaceData.layoutStyle.id) { - LayoutStyle.SPORT.id -> 56f - else -> 48f - } - - val offsetX2: Float + val timeOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { when(watchFaceData.ambientStyle) { @@ -1209,27 +1150,16 @@ class WatchCanvasRenderer( else -> 0f } -// val secondsTextScale: Float -// get() = when (watchFaceData.layoutStyle.id) { -// LayoutStyle.FOCUS.id -> { -// 18f / 14f -// } -// -// else -> { -// if (watchFaceData.detailedAmbient) { -// 14f / 14f -// } else { -// interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) -// } -// } -// } - -// val ampmTextScale: Float -// get() = if (watchFaceData.detailedAmbient) { -// 14f / 14f -// } else { -// interpolate(if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f, 14f / 14f) -// } + val complicationsOffsetX: Float + get() = when (watchFaceData.layoutStyle.id) { + LayoutStyle.SPORT.id -> { + when(watchFaceData.ambientStyle) { + AmbientStyle.DETAILED -> 0f + else -> interpolate(35f, 0f) + } + } + else -> 0f + } fun getHour(zonedDateTime: ZonedDateTime): Int { if (is24Format) { @@ -1387,13 +1317,13 @@ class WatchCanvasRenderer( canvas.withScale( // 1f, 1f, - timeTextScaleThick, - timeTextScaleThick, + timeScale, + timeScale, bounds.exactCenterX(), bounds.exactCenterY() ) { - canvas.withTranslation(offsetX2, 0f) { + canvas.withTranslation(timeOffsetX, 0f) { if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { val opacity = interpolate(0.75f, 1f) @@ -1419,8 +1349,8 @@ class WatchCanvasRenderer( val hourBmp = bitmapCache.get(cacheKey, "")!! - val hourOffsetX = timeOffsetXThick * renderScale - val hourOffsetY = hourOffsetYThick * renderScale + val hourOffsetX = 0f + val hourOffsetY = hourOffsetY * renderScale canvas.drawBitmap( hourBmp, @@ -1437,8 +1367,8 @@ class WatchCanvasRenderer( val minuteBmp = bitmapCache.get(cacheKey, "")!! - val minuteOffsetX = timeOffsetXThick * renderScale - val minuteOffsetY = minuteOffsetYThick * renderScale + val minuteOffsetX = 0f + val minuteOffsetY = minuteOffsetY * renderScale canvas.drawBitmap( minuteBmp, @@ -1479,20 +1409,20 @@ class WatchCanvasRenderer( } } - val offsetX = -// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( -// 35f, -// 0f -// ) else 0f - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && watchFaceData.ambientStyle != AmbientStyle.DETAILED) interpolate( - 35f, - 0f - ) else 0f +// val offsetX = +//// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( +//// 35f, +//// 0f +//// ) else 0f +// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && watchFaceData.ambientStyle != AmbientStyle.DETAILED) interpolate( +// 35f, +// 0f +// ) else 0f - canvas.withScale(timeTextScale, timeTextScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.withTranslation(offsetX, 0f) { + canvas.withScale(complicationsScale, complicationsScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withTranslation(complicationsOffsetX, 0f) { val compBmp = Bitmap.createBitmap( bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 @@ -1539,8 +1469,8 @@ class WatchCanvasRenderer( val ampmBmp = bitmapCache.get(cacheKey, "")!! - val offsetX = ampmOffsetX * renderScale - val offsetY = ampmOffsetY * renderScale + val offsetX = 83f * renderScale + val offsetY = 24f * renderScale compCanvas.drawBitmap( ampmBmp, @@ -1578,7 +1508,7 @@ class WatchCanvasRenderer( // drawComplications(compCanvas, zonedDateTime) // } if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - (watchFaceData.ambientStyle == AmbientStyle.DETAILED || drawProperties.timeScale != 0f) + (watchFaceData.ambientStyle == AmbientStyle.DETAILED || drawProperties.timeScale != 0f || true) // TODO @rdnt remove true ) { drawComplications(compCanvas, zonedDateTime) } @@ -1588,7 +1518,7 @@ class WatchCanvasRenderer( // val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) // val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) scale else interpolate(0f, 1f) - val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(0.75f, 1f) else interpolate(0f, 1f) + val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(0.75f, 1f) else interpolate(0.25f, 1f) // TODO @rdnt: use 0f to 1f @@ -1737,8 +1667,8 @@ class WatchCanvasRenderer( bounds, hourText, hourPaint, - timeOffsetXThick, - hourOffsetYThick, + 0f, + hourOffsetY, hourHash, "", // HOURS_BITMAP_KEY @@ -1750,8 +1680,8 @@ class WatchCanvasRenderer( bounds, minuteText, minutePaint, - timeOffsetXThick, - minuteOffsetYThick, + 0f, + minuteOffsetY, minuteHash, "", // MINUTES_BITMAP_KEY From 403f74c22fc2f22a6b22659b057d671bd1b555b1 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 24 Mar 2024 23:46:06 +0200 Subject: [PATCH 36/52] further cleanup, partial scale --- .../java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 38b01ed..ae1d7bd 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -1081,10 +1081,18 @@ class WatchCanvasRenderer( } else -> { - if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) { - 14f / 14f - } else { - interpolate(if (watchFaceData.ambientStyle == AmbientStyle.BIG_OUTLINE || watchFaceData.ambientStyle == AmbientStyle.BIG_BOLD_OUTLINE || watchFaceData.ambientStyle == AmbientStyle.BIG_FILLED) 18f / 14f else 16f / 14f, 14f / 14f) + when(watchFaceData.ambientStyle) { + AmbientStyle.BIG_OUTLINE, AmbientStyle.BIG_BOLD_OUTLINE, AmbientStyle.BIG_FILLED -> { +// interpolate(18f / 14f, 14f / 14f) + interpolate(16f / 14f, 14f / 14f) + } + + AmbientStyle.DETAILED -> 14f / 14f + + else -> { +// interpolate(16f / 14f, 14f / 14f) + interpolate(15f / 14f, 14f / 14f) + } } } } From 13dcea9f8171ac96237f626eb5ec446191089fb0 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Sun, 24 Mar 2024 23:53:31 +0200 Subject: [PATCH 37/52] rollback partial scale --- .../java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 13 +++++++------ .../rdnt/m8face/utils/HorizontalTextComplication.kt | 9 +++++---- .../dev/rdnt/m8face/utils/VerticalComplication.kt | 9 +++++---- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index ae1d7bd..a1bbd54 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -1074,6 +1074,7 @@ class WatchCanvasRenderer( when(watchFaceData.ambientStyle) { AmbientStyle.BIG_OUTLINE, AmbientStyle.BIG_BOLD_OUTLINE, AmbientStyle.BIG_FILLED -> { interpolate(18f / 16f, 16f / 16f) +// interpolate(17f / 16f, 16f / 16f) } else -> 16f / 16f @@ -1083,15 +1084,15 @@ class WatchCanvasRenderer( else -> { when(watchFaceData.ambientStyle) { AmbientStyle.BIG_OUTLINE, AmbientStyle.BIG_BOLD_OUTLINE, AmbientStyle.BIG_FILLED -> { -// interpolate(18f / 14f, 14f / 14f) - interpolate(16f / 14f, 14f / 14f) + interpolate(18f / 14f, 14f / 14f) +// interpolate(16f / 14f, 14f / 14f) } AmbientStyle.DETAILED -> 14f / 14f else -> { -// interpolate(16f / 14f, 14f / 14f) - interpolate(15f / 14f, 14f / 14f) + interpolate(16f / 14f, 14f / 14f) +// interpolate(15f / 14f, 14f / 14f) } } } @@ -1516,7 +1517,7 @@ class WatchCanvasRenderer( // drawComplications(compCanvas, zonedDateTime) // } if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - (watchFaceData.ambientStyle == AmbientStyle.DETAILED || drawProperties.timeScale != 0f || true) // TODO @rdnt remove true + (watchFaceData.ambientStyle == AmbientStyle.DETAILED || drawProperties.timeScale != 0f) ) { drawComplications(compCanvas, zonedDateTime) } @@ -1526,7 +1527,7 @@ class WatchCanvasRenderer( // val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) // val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) scale else interpolate(0f, 1f) - val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(0.75f, 1f) else interpolate(0.25f, 1f) // TODO @rdnt: use 0f to 1f + val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(0.75f, 1f) else interpolate(0f, 1f) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt index c2ed1e7..8c0aab6 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt @@ -99,10 +99,11 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat ): Bitmap { val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${debug}" - val cached = bitmapCache.get(hash) - if (cached != null) { - return cached - } + // TODO: fix as done in HorizontalComplication +// val cached = bitmapCache.get(hash) +// if (cached != null) { +// return cached +// } val bitmap = Bitmap.createBitmap( bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index 167c0c8..9ebd18d 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -129,10 +129,11 @@ class VerticalComplication(private val context: Context) : CanvasComplication { ): Bitmap { val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${debug}" - val cached = bitmapCache.get(hash) - if (cached != null) { - return cached - } + // TODO: fix as done in HorizontalComplication +// val cached = bitmapCache.get(hash) +// if (cached != null) { +// return cached +// } val bitmap = Bitmap.createBitmap( bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 From 77b16c40a2d2de4276b267acd529757bd2be8e0d Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Mon, 25 Mar 2024 00:13:02 +0200 Subject: [PATCH 38/52] Update preview img & time instant, misc --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 2 +- .../editor/WatchFaceConfigStateHolder.kt | 2 +- .../main/res/drawable-nodpi/watch_preview.png | Bin 1961 -> 2162 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index a1bbd54..1cae755 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -1332,7 +1332,7 @@ class WatchCanvasRenderer( bounds.exactCenterY() ) { - canvas.withTranslation(timeOffsetX, 0f) { + canvas.withTranslation(timeOffsetX * renderScale, 0f) { if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { val opacity = interpolate(0.75f, 1f) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index 23d6fa0..6a7411d 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -139,7 +139,7 @@ class WatchFaceConfigStateHolder( Log.d(TAG, "createWatchFacePreview()") - val instant = LocalDateTime.parse("2020-10-10T22:09:36") + val instant = LocalDateTime.parse("2020-10-10T21:30:36") .atZone(ZoneId.of("UTC")) .toInstant() diff --git a/app/src/main/res/drawable-nodpi/watch_preview.png b/app/src/main/res/drawable-nodpi/watch_preview.png index 9ed6b230a930c922d37416d5efd8d013f9b551f6..006bff3f1205b4c6f819f48486f7cd4a293f5827 100644 GIT binary patch literal 2162 zcmeAS@N?(olHy`uVBq!ia0y~yU~B+k4mP03lH*(V134|3&dvdz&dv%2Mfqu&IjIZ` z8WU?L+Ik#zkU1J3yi{qc?3;iiiq55*6to^`gz7Z4UJA>&dhp35{fs1!pcom}>rKt_ z-J$H_>9Mjgtd1X;kK8=ydC}GDPkc~@;NydZ_V?~q-`UIDlGeL2?YzZ;tZ5TH)sEC8 zy7KHf)UV;hp{!yU?4K%L^g$;6`-hKT_0DbHbN;U-+muBo8RmP~oGv-66sJD#-jS$o)H-Z5NMk2O+p|8!ZzOZCCuo_#ABc&5Z#9B@>jc998r?8mhm*Isahv3DpYjdri|9><%_ADVX>u!BK-zHiW~~ z!#ps{%g@Ggp8a`aQQyUNT=w_)KhJVGV!@Cxhrf@(fq_AQfq?}W?_@Da(XQaWQdyz< z@Zrld|L5=D|NYfxoBH1-^5wIRi~ltB`?c$n)%-R8#eEnUI2aiI7qK2WvnlTGl$$o| zW^~oeQ(Aw*fRRCgfgxm~wn9<()NFYO*Q6cDHGH`}VA)PSt;16TubsDCf4@QNQ{@2` zh6V-(lbPBIY2T)a%cs`XOuX(=x%b1W9DNODVFm^!h6^d{8fMH~%?UNP6{s?7`CR+Y zKe%K5trAxQg-OFF52d6x5)2SGF@a13IsxcB(wIO!CZ4Z2UM(&+F5_4)Q6ls_vgz{? zumkZM=Wv6~DwdI?N; zQvf)9WH8)z(5zXV9(v{P>&;t5gb4@90qYICQ*Pz&Wr4W~pO=>=N^4x)EosdHi4^=H z9nhkpw5EwjrB?*69RE1+=(_bk%O+Ty7n-9Ej$i_b{*c*)sFlB`9b1+2e9fuv2e$d8aNRJ1FiyM!I?=N z8+HjmavZ+c#S#E8_W`v5Q{UnxeGN)mZqI%3^;>dr@$;9t;^Oy{4JRD|2#k+}CfH zzxys~&6=8;kNeJ^oeR=w@kg}zpl3|nzk&n>hARhOy;^?b?q8|s==iwW`rUUs%XSOD zemy_Au+Y+Nar=&a|6V?DU|6v$EL$^b?Nd$p-7;&h1u4$I9=$F`?d0oI8TZ2dKg_w$ z!~`rqXB`n0=rw*hD^)IByV7=fwmc-Y=RfA+C^CD=ni{#j@Kco>BvtQVYKlp@t8DWA zIU~3rI^eyoL4mz8_WF)*kf=0}U)PW^Co|5&^rhkFd&*B-<7P0dXubGC(QWZ>P5Em1 z#TT2Mfn`{!flS=dpFbm)ygXF@|J(b=kLO$1+jl?waB87r eh}M2!&L7tD72kzVonXph00K`}KbLh*2~7ZVjdiO4 literal 1961 zcmeAS@N?(olHy`uVBq!ia0y~yU~B+k4mP03lH*(V139gk&dvdz&dv%2Mfqu&IjIZ` z8WU?L+Ik#zkZ8NV%*$1auT10BgoRd-0-}Xe!gQKiFNI}XJ^193ea58;OLkartz%`c z7hBDzcW#%}4z8vL{2i;iCok%r@*_4VL-4WeaoauL_v)A5XLQ-c@gA zjHp48&H^^kNsrn8H{aiD+I=}VFUWxH%f1`mj!OvKV2$Fv{m*XO+iRQ;>e<$9>Dzye z<(Zvle#w>-f(AQyV`9opD*1QZVLar&E5)Pl$#PYZ?gPIiYlGz^cCr4terMm-^M$_{ zQdWkaHa~Ds(I9Too6`RB@1@`0-k(+Wzm6d&qG;A}rRUEW7+CLnx;TbZ+uST{^h(r69RXm#KKkqIxBGH@*v*%L1RW@bcCs^`CbkIx&rKykuRk>^b*ohw9V!?`qFp`*`l&_ld<{Sk!_N9|CX0g`OkT+HHPnEppGg8RKR#8jC3E4VtoP5C9iG3$>&PRsXR zPLP2Qc$Rg{xXZ>MOo2Cnp@Tn^3f6Q-JFI#>-|}VY{QBh!?eD*+`uWd*5gw)(;RJKB z$w{*djW^PNm|21sdRPv zE3t@Z7oZ*ZJp!~==73p%#+q+o{z&eJI0)uwBqK_W02QAJ^GA&bBn3#!oK3tX3g_)N zf8V+2TiLm|ee6IBkfgCQWfHnLR&z5FWB!$bLhfT9Kjv+GJ?r}RlYes_)Fm;&(-S;E zkb(=$RLEgg{dM!*jnid*--U@uB(rmvms&+@90aCOT+sxJyn?80jgxdrS3iG!JLK#8 zuSM|(@M{7GZ~@b{#-Q5O>H2Q}Yqm`TdI6jtaJt`tfx)67cLC4-uR^d0!{;uO!)5`G z9@mx$6L1PRHXL|^T_RXH6Nm^#pfhl~6HBs$#X`pMqq+}z=FR)3y8qkn-`QX9=WqYL z?sd$1{gdnO?&5IKRa*O zd)&gbzNQ^qSR}CYHM}?#xVr%oIv32{9a3KZZTDDO_V3A>s&66u5Toy#x*mCB#( Date: Mon, 25 Mar 2024 00:34:28 +0200 Subject: [PATCH 39/52] rollback instant change, bump version code --- app/build.gradle | 2 +- .../editor/WatchFaceConfigStateHolder.kt | 4 +++- .../main/res/drawable-nodpi/watch_preview.png | Bin 2162 -> 2163 bytes .../drawable-nodpi/watch_preview_subtle.png | Bin 1724 -> 1706 bytes 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9fdf042..8efa80a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,7 +27,7 @@ android { applicationId "dev.rdnt.m8face" minSdk 28 targetSdk 33 - versionCode 56 + versionCode 57 versionName '3.0.0' } diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index 6a7411d..42a1476 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -139,7 +139,9 @@ class WatchFaceConfigStateHolder( Log.d(TAG, "createWatchFacePreview()") - val instant = LocalDateTime.parse("2020-10-10T21:30:36") + // actual watch uses this date and not 09:30:36. changed to 22 instead of 10 + // for military time to be visible + val instant = LocalDateTime.parse("2020-10-10T22:09:36") .atZone(ZoneId.of("UTC")) .toInstant() diff --git a/app/src/main/res/drawable-nodpi/watch_preview.png b/app/src/main/res/drawable-nodpi/watch_preview.png index 006bff3f1205b4c6f819f48486f7cd4a293f5827..2ba7c7176b7fa22e1b5edffce7dc36c458a2d922 100644 GIT binary patch delta 1068 zcmew)@L6EP7RI<$o-U3d6}R5rwfB-qlsNFP{IG)duGT|B0$V01966S>wrQ=Lg6p~m zqBc$8AGqWkt5@etJml8Y%BZN5vG9;ki?6!ZoDIh3`aVCeT(#i!s`-ub@7sUh>g@1j zxL_qO!#FvRMRBqKha8mVu6Jk@a(57GZ(qLC{@a6u?^mB!{`;0`_uiym|8up^?|ofX z^Vi68t1&RJFf`QfWK!C`Cfq#KtXq4T(fs9Sw2gci7&sUhN}q6b9J>1S7CVId_90h? z(~`epr%bLphVC#r^~+TJt8_rB9sdjl1_6c!vtm}4y!^_Kf!B^tueA0Jxx&4E)wd=i z14ae~28P^cTpf2}d>c3!7#JBA=*2KjjGkQf@603pf}gA7!1@_hEfy9OKF*N9G+B_z zP6?>W7wChy=GmU>`la>CrT+dkjg76VgQ#Tz8Z%juMT$r3q}c_wje9pq-6`8V!vB7zN48+k>vl-`G(-xHmXD|nP;@<(Q= zNh}ZTbrity($dxFcU)q1IY0llLJ^pIZczKHAK#Y`TI5l_-g8t}TBFBezz8*1B%tkxpvm!%6OXQ2|8v{@!g-<= z2vc}kau>MNZnM6YYQ6mPBsLW;kPkHH>P`U%gpzQE?rn#YxTdbQ7dCmgaw5)4i;|e%rJpCQzVEHeli4uVCNSc5ve20J=+-dx237vuaH*z# zKYI7>7-J*Dg&Y4fPR4xsy?W!meP6$9yM6oqM3v&HKc)Ej{pbIyE!Z&o<;O_d`hSb& zv9mOIMaJLnI+&pU>+jWws9!%G)k;g>zW9Fqv5YU;ZHFiC*z@P%0SAVRrm0i?H*WrH zYG%HF&#!;=K%1-fK8=X}E%W(vx1pT%zLK{I1(UQqH|&q5>j1Z&_0#*B5qG z$w5-*4yGojl)K6%ub(ptGcYhQ9PnP(uzIEa;V~uz~hg9r}Xdb|M=tJ!bZ*pHHqGJkN*78nUwNwfBo<8A3yrfnLqz% uM#<9)3tZ-Q%&=f<_%``HdyaU|A6B`y-#)sX+_I1X2s~Z=T-G@yGywo%X|j?4 delta 1066 zcmew?@JV397RK0>o-U3d6}R5r-R~8hC~@H7_bJUTCe4ij0woiat{heLI~uCLz&Zb5 z^a<4p?t4ws7wir;E-9G!D8W&KQ8t9b)x$h6%gfKka-RKpV^QD5bzJuM_&?8bIby+( zF^9izav_`6i28cC#(5u{)_uCFmNz1{4ZiXa%NN9-6=P1*3IaunWwb=gaOb128NJ{+6qPCQ?unE zT$6Sn*YM@`fMq-Rv<^=VymsDl{rv{5Pn8E)7#bKDOlE2;q!TQl*xOXc1V zt8(-;n1vY_m>4dktZSGtb2TT_;8vj0u;p{@KmXv4`L{}3je!AZ#3v7>q&E@_lMUHa zCOdFQivwL?;`xf>)#7sFGLH2UB|^_5n?4_5nH<2R#OrW_%_}5*cXH^qVk>`;4i29b zX^qy+twJDS~i4*`$Dxz$tXd>_XJa-_wq*%6Y!#)c1qif_)}Ca!5@M z;NTR0f50qYg+;WjAvlPE!OsK=JO!YUufguGkXtE-cC|7{uwnnFOIbR_`C3e7NTjRA8 zA1~fe{9)I?2{sF8?BoPy(aG`5tegwZOzPOMOW*(~Ty=piq*JUzhCBV-@aejSKRBfrJUS6 zyT7~d-#`83$C`Qe^>OE!nH*N;g#$=9bc?uGk*m~)?r2^eCtj))5M8o!*CDi^L@X}dgI9ulqj zkGVLC%wDplMy@aXR3!&Vi#wQ_Vp8rZo4kL{2u`U7yw^1-gt1q~Uf=Nz61oQR>l!lV zWX5@zzBK%NPx*;!+zf^ltruS?x-I^#DPJwW_+qm&FqM}Y$iyA}`7?6K%R}}5zrBC_ zc)o?befPr;rxrRkPU?`bW@~sh`2%~7_?$ni0Lw_Q*9qdrVAwzYtAS&W0RV;#q(pG5I!Q|2}XktiGTpR`0f`cE6 zRR;wX;s~)=?qa!%S;bI^r-);Us!_g>cUj@Q#aXS@ zS^J*+g~6h>oaH*rVI;AL6w(kOqmC+Sun?zJBgI69_7fidA;+I4mrSlI7&+##0Tq(t z2mgcL-I}Gz34b>!m;icTZ2Myb2&@IM1rTF1Z91ZF--Z*;WS5fI)6 zF0MP8ya!zF0MRF1G9*Xx(-cZ2;QfrgsQ?V#0{yFQZ|!}YJ^(rDYUu_zI0Qz^l)dKh p?!NBc{yo#~?*~J8a+CmU^CbWP1mQ_UK~#9!?c6brgR{~B>I6ZUz3~75 delta 399 zcmV;A0dW4R4ZIDILw}`I9NIy|AwzX)K~%(1s#pXIrLEAagUO{|(8Q3WxHt-~1qVMC zs}3&Cx;nTDg5U>;tBaGOi;))n} zg@7O;h$AF1Q%~m>^6(sA_we!cF2S?B&;2 Date: Mon, 25 Mar 2024 00:35:43 +0200 Subject: [PATCH 40/52] update-changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f07c92..3745d07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -Last updated: 2023-11-25 +Last updated: 2024-03-25 + +- v3.0.0-alpha + - Dynamic layouts - v2.12.1 - Remove heartrate complication prefix style From 97053a14a745c9ba85338b27c74d50e8fcedf975 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Mon, 25 Mar 2024 00:52:05 +0200 Subject: [PATCH 41/52] update app name --- app/src/main/res/values/strings.xml | 4 ++-- settings.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b2f246..d5ae957 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,9 +14,9 @@ limitations under the License. --> - m8face + M8 - m8face + M8 Layout Style diff --git a/settings.gradle b/settings.gradle index 4b87ba7..f299f1c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,4 +33,4 @@ dependencyResolutionManagement { } include ":app" -rootProject.name = "m8face" +rootProject.name = "m8" From b1e796b5813a4a902b28d1de546e4864c3a14f36 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Mon, 25 Mar 2024 01:06:10 +0200 Subject: [PATCH 42/52] clean up manifest --- app/src/main/AndroidManifest.xml | 113 ++++++++++++++-------------- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 58 insertions(+), 57 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4ef19fa..55890ee 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - - + xmlns:tools="http://schemas.android.com/tools"> - + - - - - - - + + + + + + - + - + - - - - - + + + + + - - - + + + - + - - + + - + - - + + - - - - - + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d5ae957..d9f9aa1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,7 +16,7 @@ M8 - M8 + M8 Layout Style From 281a9257db95e209ad480235fb232d259b5cc9ac Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Mon, 25 Mar 2024 01:09:05 +0200 Subject: [PATCH 43/52] misc --- app/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 55890ee..20859cf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -71,7 +71,7 @@ android:name=".editor.WatchFaceConfigActivity" android:exported="true" android:label="@string/title_activity_watch_face_config" - android:taskAffinity="dev.rdnt.m8face.editor.WatchFaceConfigActivity"> + android:taskAffinity="dev.rdnt.m8face"> From 5ecb53d47c21bf9a3dd3e0489affaa5c7c84b331 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Tue, 26 Mar 2024 00:30:05 +0200 Subject: [PATCH 44/52] use lru cache for bitmap atlas storage --- .../main/java/dev/rdnt/m8face/BitmapCache.kt | 2 +- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 267 +++--------------- .../m8face/data/watchface/WatchFaceData.kt | 1 - .../m8face/editor/WatchFaceConfigActivity.kt | 1 - .../editor/WatchFaceConfigStateHolder.kt | 19 -- .../m8face/utils/HorizontalComplication.kt | 4 +- .../utils/HorizontalTextComplication.kt | 4 +- .../rdnt/m8face/utils/UserStyleSchemaUtils.kt | 12 - .../rdnt/m8face/utils/VerticalComplication.kt | 5 +- 9 files changed, 46 insertions(+), 269 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt index eb2b9e9..624f911 100644 --- a/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt +++ b/app/src/main/java/dev/rdnt/m8face/BitmapCache.kt @@ -11,7 +11,7 @@ import android.util.Log //const val TIME_BITMAP_KEY = "time" //const val AUX_BITMAP_KEY = "aux" -class BitmapCache { +class BitmapCacheOld { private val entries: MutableMap = mutableMapOf() override fun toString(): String { diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 1cae755..2b5aee9 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -26,6 +26,7 @@ import android.graphics.Rect import android.graphics.RectF import android.util.FloatProperty import android.util.Log +import android.util.LruCache import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep @@ -48,7 +49,6 @@ import dev.rdnt.m8face.data.watchface.WatchFaceColorPalette.Companion.convertToW import dev.rdnt.m8face.data.watchface.WatchFaceData import dev.rdnt.m8face.utils.AMBIENT_STYLE_SETTING import dev.rdnt.m8face.utils.COLOR_STYLE_SETTING -import dev.rdnt.m8face.utils.DEBUG_SETTING import dev.rdnt.m8face.utils.HorizontalComplication import dev.rdnt.m8face.utils.HorizontalTextComplication import dev.rdnt.m8face.utils.LAYOUT_STYLE_SETTING @@ -67,11 +67,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch - -// Default for how long each frame is displayed at expected frame rate. -private const val DEFAULT_INTERACTIVE_DRAW_MODE_UPDATE_DELAY_MILLIS: Long = 16 - - +private const val debug = false /** * Renders watch face via data in Room database. Also, updates watch face state based on setting @@ -90,7 +86,7 @@ class WatchCanvasRenderer( currentUserStyleRepository, watchState, canvasType, - DEFAULT_INTERACTIVE_DRAW_MODE_UPDATE_DELAY_MILLIS, + 60000, clearWithBackgroundTintBeforeRenderingHighlightLayer = false ) { class AnalogSharedAssets : SharedAssets { @@ -115,7 +111,6 @@ class WatchCanvasRenderer( secondsStyle = SecondsStyle.NONE, militaryTime = true, layoutStyle = LayoutStyle.INFO1, - debug = false, ) // Converts resource ids into Colors and ComplicationDrawable. @@ -258,14 +253,10 @@ class WatchCanvasRenderer( private var is24Format: Boolean = watchFaceData.militaryTime - private var debug: Boolean = watchFaceData.debug - private var drawProperties = DrawProperties() // timeScale = 0f private var isHeadless = false private var isAmbient = false - private var bitmapCache: BitmapCache = BitmapCache() - private var interactiveFrameDelay: Long = 16 // private var animating: Boolean = false @@ -456,6 +447,9 @@ class WatchCanvasRenderer( } } + private lateinit var memoryCache: LruCache + var memInit = false + override suspend fun createSharedAssets(): AnalogSharedAssets { return AnalogSharedAssets() } @@ -531,14 +525,6 @@ class WatchCanvasRenderer( militaryTime = booleanValue.value, ) } - - DEBUG_SETTING -> { - val booleanValue = options.value as UserStyleSetting.BooleanUserStyleSetting.BooleanOption - - newWatchFaceData = newWatchFaceData.copy( - debug = booleanValue.value, - ) - } } } @@ -640,8 +626,6 @@ class WatchCanvasRenderer( } is24Format = watchFaceData.militaryTime -// debug = watchFaceData.debug - debug = false interactiveFrameDelay = when (watchFaceData.secondsStyle.id) { SecondsStyle.NONE.id -> if (shouldDrawSeconds) 1000 else 60000 @@ -846,7 +830,7 @@ class WatchCanvasRenderer( val cacheKey = "hour_normal_$hour" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -859,7 +843,7 @@ class WatchCanvasRenderer( }) val cacheKey = "hour_outline_$hour" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -872,7 +856,7 @@ class WatchCanvasRenderer( }) val cacheKey = "hour_big_outline_$hour" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -885,7 +869,7 @@ class WatchCanvasRenderer( }) val cacheKey = "hour_bold_outline_$hour" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -898,7 +882,7 @@ class WatchCanvasRenderer( }) val cacheKey = "hour_big_bold_outline_$hour" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -912,7 +896,7 @@ class WatchCanvasRenderer( val cacheKey = "minute_normal_$minute" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -925,7 +909,7 @@ class WatchCanvasRenderer( }) val cacheKey = "minute_outline_$minute" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -938,7 +922,7 @@ class WatchCanvasRenderer( }) val cacheKey = "minute_big_outline_$minute" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -951,7 +935,7 @@ class WatchCanvasRenderer( }) val cacheKey = "minute_bold_outline_$minute" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -964,7 +948,7 @@ class WatchCanvasRenderer( }) val cacheKey = "minute_big_bold_outline_$minute" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -976,7 +960,7 @@ class WatchCanvasRenderer( textSize *= scale }) val cacheKey = "second_normal_$second" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -986,7 +970,7 @@ class WatchCanvasRenderer( textSize *= scale }) val cacheKey = "ampm_$text" - bitmapCache.set(cacheKey, "", bmp) + memoryCache.put(cacheKey, bmp) } } @@ -1203,7 +1187,16 @@ class WatchCanvasRenderer( canvas.drawColor(Color.parseColor("#ff000000")) - if (!this::state.isInitialized) { + if (!memInit) { + // Use 1/8th of the available memory for this memory cache. + val maxItems = 1000 + + memoryCache = LruCache(maxItems) + + memInit = true + } + + if (!this::state.isInitialized || !this::memoryCache.isInitialized) { // placeholder return } @@ -1356,7 +1349,7 @@ class WatchCanvasRenderer( val cacheKey = "hour_${textStyle}_$hourText" - val hourBmp = bitmapCache.get(cacheKey, "")!! + val hourBmp = memoryCache.get(cacheKey) val hourOffsetX = 0f val hourOffsetY = hourOffsetY * renderScale @@ -1374,7 +1367,7 @@ class WatchCanvasRenderer( val minuteText = zonedDateTime.minute.toString().padStart(2, '0') val cacheKey = "minute_${textStyle}_$minuteText" - val minuteBmp = bitmapCache.get(cacheKey, "")!! + val minuteBmp = memoryCache.get(cacheKey) val minuteOffsetX = 0f val minuteOffsetY = minuteOffsetY * renderScale @@ -1445,7 +1438,7 @@ class WatchCanvasRenderer( val secondText = if (false && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') val cacheKey = "second_normal_$secondText" - val secondBmp = bitmapCache.get(cacheKey, "")!! + val secondBmp = memoryCache.get(cacheKey) val offsetX = secondsOffsetX * renderScale val offsetY = secondsOffsetY * renderScale @@ -1476,7 +1469,7 @@ class WatchCanvasRenderer( val ampmText = getAmPm(zonedDateTime).uppercase() val cacheKey = "ampm_$ampmText" - val ampmBmp = bitmapCache.get(cacheKey, "")!! + val ampmBmp = memoryCache.get(cacheKey) val offsetX = 83f * renderScale val offsetY = 24f * renderScale @@ -1544,11 +1537,12 @@ class WatchCanvasRenderer( } if (debug) { - Log.d("WatchCanvasRenderer", "render took ${took.toFloat() / 1000000.0}ms, $bitmapCache") + Log.d("WatchCanvasRenderer", "render took ${took.toFloat() / 1000000.0}ms, cache ${memoryCache.size()} / ${memoryCache.maxSize()}") } - if (!(ambientExitAnimator.isRunning || ambientEnterAnimator.isRunning)) { + if (!(ambientExitAnimator.isRunning || ambientEnterAnimator.isRunning) && animating) { animating = false + interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay } // if (ambientExitAnimator.isRunning) { @@ -1593,7 +1587,7 @@ class WatchCanvasRenderer( } override fun shouldAnimate(): Boolean { - return super.shouldAnimate() || ambientEnterAnimator.isRunning || animating + return super.shouldAnimate() || animating } // ----- All drawing functions ----- @@ -1620,21 +1614,21 @@ class WatchCanvasRenderer( is VerticalComplication -> { // (complication.renderer as VerticalComplication).opacity = opacity // (complication.renderer as VerticalComplication).scale = scale - (complication.renderer as VerticalComplication).debug = debug +// (complication.renderer as VerticalComplication).debug = debug } is HorizontalComplication -> { // (complication.renderer as HorizontalComplication).opacity = opacity // (complication.renderer as HorizontalComplication).offsetX = offsetX // (complication.renderer as HorizontalComplication).scale = scale - (complication.renderer as HorizontalComplication).debug = debug +// (complication.renderer as HorizontalComplication).debug = debug } is HorizontalTextComplication -> { // (complication.renderer as HorizontalTextComplication).opacity = opacity // (complication.renderer as HorizontalTextComplication).offsetX = offsetX // (complication.renderer as HorizontalTextComplication).scale = scale - (complication.renderer as HorizontalTextComplication).debug = debug +// (complication.renderer as HorizontalTextComplication).debug = debug } else -> {} @@ -1645,189 +1639,6 @@ class WatchCanvasRenderer( } } - private fun renderTime( - canvas: Canvas, - bounds: Rect, - zonedDateTime: ZonedDateTime, - hourPaint: Paint, - minutePaint: Paint, - ) { - val hourText = getHour(zonedDateTime).toString().padStart(2, '0') - val hourHash = "$hourText,${hourPaint.textSize},${hourPaint.color},$debug" - - val minuteText = zonedDateTime.minute.toString().padStart(2, '0') - val minuteHash = "$minuteText,${minutePaint.textSize},${minutePaint.color},$debug" - -// val timeHash = "$hourHash,$minuteHash" -// -// val cached = bitmapCache.get(TIME_BITMAP_KEY, timeHash) -// if (cached != null) { -// return cached -// } - -// val timeBmp = Bitmap.createBitmap( -// bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 -// ) -// -// val timeCanvas = Canvas(timeBmp) - - drawText2( - canvas, - bounds, - hourText, - hourPaint, - 0f, - hourOffsetY, - hourHash, - "", -// HOURS_BITMAP_KEY - ) - - - drawText2( - canvas, - bounds, - minuteText, - minutePaint, - 0f, - minuteOffsetY, - minuteHash, - "", -// MINUTES_BITMAP_KEY - ) - -// if (debug) { -// canvas.drawRect(bounds, Paint().apply { -// this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) -// style = Paint.Style.STROKE -// strokeWidth = 2f -// }) -// val p2 = Paint() -// p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) -// p2.typeface = context.resources.getFont(R.font.m8stealth57) -// p2.textSize = 8f -// canvas.drawText( -// "r ${bitmapCache.loads(TIME_BITMAP_KEY)}", -// 3f, -// bounds.height().toFloat()/2 - 7f, -// p2, -// ) -// -// canvas.drawText( -// "w ${bitmapCache.renders(TIME_BITMAP_KEY)}", -// 3f, -// bounds.height().toFloat()/2 + 8f, -// p2, -// ) -// } - -// bitmapCache.set(TIME_BITMAP_KEY, timeHash, timeBmp) - -// return canvas - } - - private fun drawText( - canvas: Canvas, - bounds: Rect, - text: String, - paint: Paint, - offsetX: Float, - offsetY: Float, - cacheKey: String, - ) { - val hash = "${text},${paint.textSize},${paint.color},${debug}" - - val bitmap = renderText(canvas, bounds, text, paint, cacheKey, hash) - - canvas.drawBitmap( - bitmap, - 192f - bitmap.width / 2 + offsetX, - 192f - bitmap.height / 2 + offsetY, - Paint(), - ) - } - - private fun drawText2( - canvas: Canvas, - bounds: Rect, - text: String, - paint: Paint, - offsetX: Float, - offsetY: Float, - hash: String, - cacheKey: String, - ) { - val bitmap = renderText(canvas, bounds, text, paint, cacheKey, hash) - - canvas.drawBitmap( - bitmap, - 192f - bitmap.width / 2 + offsetX, - 192f - bitmap.height / 2 + offsetY, - Paint(), - ) - } - - private fun renderText( - canvas: Canvas, - bounds: Rect, - text: String, - paint: Paint, - cacheHash: String, - cacheKey: String, - ): Bitmap { -// if (debug) { -// canvas.drawRect(bounds, Paint().apply { -// this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) -// style = Paint.Style.STROKE -// strokeWidth = 2f -// }) -// val p2 = Paint() -// p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aaf2e900"), 1f) -// p2.typeface = context.resources.getFont(R.font.m8stealth57) -// p2.textSize = 8f -// canvas.drawText( -// "r ${bitmapCache.loads(cacheKey)}", -// 3f, -// bounds.height().toFloat() - 12f, -// p2, -// ) -// -// canvas.drawText( -// "w ${bitmapCache.renders(cacheKey)}", -// 3f, -// bounds.height().toFloat() - 3f, -// p2, -// ) -// } - - val cached = bitmapCache.get(cacheKey, cacheHash) - if (cached != null) { - return cached - } - - val p = Paint(paint) - - var textBounds = Rect() - p.getTextBounds(text, 0, text.length, textBounds) - textBounds = Rect(0, 0, textBounds.width(), textBounds.height()) - - val bmp = Bitmap.createBitmap( - textBounds.width(), textBounds.height(), Bitmap.Config.ARGB_8888 - ) - val bmpCanvas = Canvas(bmp) - - bmpCanvas.drawText( - text, - 0f, - textBounds.height().toFloat(), - p, - ) - - bitmapCache.set(cacheKey, cacheHash, bmp) - - return bmp - } - private fun drawDashes( canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime ) { diff --git a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt index 3165239..bc761aa 100644 --- a/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt +++ b/app/src/main/java/dev/rdnt/m8face/data/watchface/WatchFaceData.kt @@ -24,5 +24,4 @@ data class WatchFaceData( val ambientStyle: AmbientStyle = AmbientStyle.OUTLINE, val secondsStyle: SecondsStyle = SecondsStyle.NONE, val militaryTime: Boolean = true, - val debug: Boolean = false, ) diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt index 2336e14..bd5e941 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigActivity.kt @@ -347,7 +347,6 @@ fun WatchfaceConfigApp( val secondsStyleIndex = secondsStyles.indexOfFirst { it.id == state.userStylesAndPreview.secondsStyleId } val militaryTimeEnabled = state.userStylesAndPreview.militaryTime -// val debugEnabled = state.userStylesAndPreview.debug Box( Modifier diff --git a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt index 42a1476..31ec2f7 100644 --- a/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt +++ b/app/src/main/java/dev/rdnt/m8face/editor/WatchFaceConfigStateHolder.kt @@ -65,9 +65,6 @@ class WatchFaceConfigStateHolder( private lateinit var ambientStyleKey: UserStyleSetting.ListUserStyleSetting private lateinit var secondsStyleKey: UserStyleSetting.ListUserStyleSetting private lateinit var militaryTimeKey: UserStyleSetting.BooleanUserStyleSetting - private lateinit var bigAmbientKey: UserStyleSetting.BooleanUserStyleSetting - private lateinit var detailedAmbientKey: UserStyleSetting.BooleanUserStyleSetting - private lateinit var debugKey: UserStyleSetting.BooleanUserStyleSetting val uiState: StateFlow = flow { @@ -121,10 +118,6 @@ class WatchFaceConfigStateHolder( MILITARY_TIME_SETTING -> { militaryTimeKey = setting as UserStyleSetting.BooleanUserStyleSetting } - - DEBUG_SETTING -> { - debugKey = setting as UserStyleSetting.BooleanUserStyleSetting - } } } } @@ -169,16 +162,12 @@ class WatchFaceConfigStateHolder( val militaryTime = userStyle[militaryTimeKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption - val debug = - userStyle[debugKey] as UserStyleSetting.BooleanUserStyleSetting.BooleanOption - return UserStylesAndPreview( layoutStyleId = layoutStyle.id.toString(), colorStyleId = colorStyle.id.toString(), ambientStyleId = ambientStyle.id.toString(), secondsStyleId = secondsStyle.id.toString(), militaryTime = militaryTime.value, - debug = debug.value, previewImage = bitmap, ) } @@ -349,13 +338,6 @@ class WatchFaceConfigStateHolder( ) } - fun setDebug(enabled: Boolean) { - setUserStyleOption( - debugKey, - UserStyleSetting.BooleanUserStyleSetting.BooleanOption.from(enabled) - ) - } - // Saves User Style Option change back to the back to the EditorSession. // Note: The UI widgets in the Activity that can trigger this method (through the 'set' methods) // will only be enabled after the EditorSession has been initialized. @@ -387,7 +369,6 @@ class WatchFaceConfigStateHolder( val ambientStyleId: String, val secondsStyleId: String, val militaryTime: Boolean, - val debug: Boolean, val previewImage: Bitmap, ) diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index 352b1f0..d98534a 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -31,6 +31,8 @@ import java.time.Instant import java.time.ZonedDateTime import kotlin.math.max +private const val debug = false + class HorizontalComplication(private val context: Context) : CanvasComplication { private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() @@ -38,8 +40,6 @@ class HorizontalComplication(private val context: Context) : CanvasComplication Log.d("HorizontalComplication", "Constructor ran") } - var debug: Boolean = false; - var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { field = tertiaryColor diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt index 8c0aab6..6c92d42 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt @@ -28,11 +28,11 @@ import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime +private const val debug = false + class HorizontalTextComplication(private val context: Context) : CanvasComplication { private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() - var debug: Boolean = false; - var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { field = tertiaryColor diff --git a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt index 8957f84..9ed8649 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/UserStyleSchemaUtils.kt @@ -43,7 +43,6 @@ const val COLOR_STYLE_SETTING = "color_style_setting" const val AMBIENT_STYLE_SETTING = "ambient_style_setting" const val SECONDS_STYLE_SETTING = "seconds_style_setting" const val MILITARY_TIME_SETTING = "military_time_setting" -const val DEBUG_SETTING = "debug_setting" /* * Creates user styles in the settings activity associated with the watch face, so users can @@ -375,16 +374,6 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { DateFormat.is24HourFormat(context), // default ) - val debugSetting = UserStyleSetting.BooleanUserStyleSetting( - UserStyleSetting.Id(DEBUG_SETTING), - context.resources, - R.string.debug_setting, - R.string.debug_setting_description, - null, - listOf(WatchFaceLayer.BASE), - DateFormat.is24HourFormat(context), // default - ) - // 4. Create style settings to hold all options. return UserStyleSchema( listOf( @@ -393,7 +382,6 @@ fun createUserStyleSchema(context: Context): UserStyleSchema { ambientStyleSetting, secondsStyleSetting, militaryTimeSetting, - debugSetting, ) ) } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index 9ebd18d..965585f 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -12,17 +12,16 @@ import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.complications.data.* -import dev.rdnt.m8face.BitmapCache import dev.rdnt.m8face.BitmapCacheEntry import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime +private const val debug = false + class VerticalComplication(private val context: Context) : CanvasComplication { private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() - var debug: Boolean = false; - var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { field = tertiaryColor From ac73cdec970d477499dd77334ae0f4f6d1c0a2b7 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Tue, 26 Mar 2024 00:34:43 +0200 Subject: [PATCH 45/52] Use LruCache for watchface renderer --- CHANGELOG.md | 3 +++ app/build.gradle | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3745d07..016f939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ Last updated: 2024-03-25 +- v3.0.1-alpha + - Use LruCache instead of BitmapCache + - v3.0.0-alpha - Dynamic layouts diff --git a/app/build.gradle b/app/build.gradle index 8efa80a..af65f52 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { applicationId "dev.rdnt.m8face" minSdk 28 targetSdk 33 - versionCode 57 - versionName '3.0.0' + versionCode 58 + versionName '3.0.1' } buildFeatures { From a20d40c49cabcf881952aa4124d3b730abe56a18 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 27 Mar 2024 16:47:57 +0200 Subject: [PATCH 46/52] before debug anim --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 390 ++++-------------- .../m8face/utils/HorizontalComplication.kt | 22 +- .../utils/HorizontalTextComplication.kt | 101 ++--- 3 files changed, 139 insertions(+), 374 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 2b5aee9..5ea40c1 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -30,7 +30,9 @@ import android.util.LruCache import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep +import androidx.core.animation.doOnEnd import androidx.core.graphics.ColorUtils +import androidx.core.graphics.set import androidx.core.graphics.withRotation import androidx.core.graphics.withScale import androidx.core.graphics.withTranslation @@ -56,7 +58,9 @@ import dev.rdnt.m8face.utils.MILITARY_TIME_SETTING import dev.rdnt.m8face.utils.SECONDS_STYLE_SETTING import dev.rdnt.m8face.utils.VerticalComplication import java.time.Duration +import java.time.Instant import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt @@ -68,6 +72,7 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch private const val debug = false +private const val debugTiming = false /** * Renders watch face via data in Room database. Also, updates watch face state based on setting @@ -268,14 +273,13 @@ class WatchCanvasRenderer( private val ambientExitAnimator = AnimatorSet().apply { - interpolator = - AnimationUtils.loadInterpolator( - context, - android.R.interpolator.linear_out_slow_in - ) play( ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1.0f).apply { - duration = ambientTransitionMs + interpolator = + AnimationUtils.loadInterpolator( + context, + android.R.interpolator.accelerate_decelerate + ) setAutoCancel(true) }, ) @@ -284,10 +288,10 @@ class WatchCanvasRenderer( // Animation played when entering ambient mode. private val ambientEnterAnimator = AnimatorSet().apply { - interpolator = AnimationUtils.loadInterpolator( - context, - android.R.interpolator.fast_out_linear_in - ) +// interpolator = AnimationUtils.loadInterpolator( +// context, +// android.R.interpolator.decelerate_cubic +// ) // val keyframes = arrayOf( // Keyframe.ofFloat(0f, 1f), @@ -298,13 +302,15 @@ class WatchCanvasRenderer( // DrawProperties.TIME_SCALE, *keyframes, Keyframe.ofFloat(1f, 0f) // ) - play( -// ObjectAnimator.ofPropertyValuesHolder(drawProperties, propertyValuesHolder).apply { -// duration = AMBIENT_TRANSITION_MS -// setAutoCancel(false) -// }, + playSequentially( + ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale).apply { + setAutoCancel(true) + }, ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 0.0f).apply { - duration = ambientTransitionMs + interpolator = AnimationUtils.loadInterpolator( + context, + android.R.interpolator.accelerate_decelerate + ) setAutoCancel(true) }, ) @@ -316,7 +322,10 @@ class WatchCanvasRenderer( // preloadBitmaps() // } - private var animating = false +// private var animating = false + private var animatingEnter = false + private var animatingExit = false + private var animationDone = false private lateinit var state: UserStyle @@ -344,21 +353,21 @@ class WatchCanvasRenderer( isAmbient = ambient!! if (!watchState.isHeadless) { - animating = true - interactiveDrawModeUpdateDelayMillis = 16 +// animating = true if (isAmbient) { // Log.d("@@@", "ambient on") // drawProperties.timeScale = 0f // ambientEnterAnimator.setupStartValues() + interactiveDrawModeUpdateDelayMillis = 33 // Log.d("@@@", "cancel before enter") -// ambientEnterAnimator.removeAllListeners() -// ambientExitAnimator.removeAllListeners() + ambientEnterAnimator.removeAllListeners() + ambientExitAnimator.removeAllListeners() // -// ambientEnterAnimator.cancel() -// ambientExitAnimator.cancel() + ambientEnterAnimator.cancel() + ambientExitAnimator.cancel() // // interactiveDrawModeUpdateDelayMillis = 16 @@ -376,7 +385,7 @@ class WatchCanvasRenderer( //// interactiveDrawModeUpdateDelayMillis = 16 // } // } - ambientExitAnimator.cancel() +// ambientExitAnimator.cancel() // enteringInteractive = false // enteringAmbient = true @@ -389,21 +398,37 @@ class WatchCanvasRenderer( // Log.d("@@@", ambientTransitionMs.toString()) - ambientEnterAnimator.setupStartValues() - ambientEnterAnimator.duration = (drawProperties.timeScale * ambientTransitionMs.toFloat()).toLong() + animatingEnter = true + animationDone = false + +// ambientEnterAnimator.doOnEnd { +// animatingEnter = false +// } + ambientEnterAnimator.setupStartValues() +// ambientEnterAnimator.duration = (drawProperties.timeScale * ambientTransitionMs.toFloat()).toLong() + ambientEnterAnimator.childAnimations[0].duration = (drawProperties.timeScale * (ambientTransitionMs/2).toFloat()).toLong() + ambientEnterAnimator.childAnimations[1].duration = (drawProperties.timeScale * (ambientTransitionMs/2*3).toFloat()).toLong() // interactiveDrawModeUpdateDelayMillis = 16 ambientEnterAnimator.start() } else { + interactiveDrawModeUpdateDelayMillis = 16 // Log.d("@@@", "ambient off") // interactiveDrawModeUpdateDelayMillis = 16 // Log.d("@@@", "cancel before exit") -// ambientEnterAnimator.removeAllListeners() -// ambientExitAnimator.removeAllListeners() + ambientEnterAnimator.removeAllListeners() + ambientExitAnimator.removeAllListeners() // -// ambientEnterAnimator.cancel() -// ambientExitAnimator.cancel() + ambientEnterAnimator.cancel() + ambientExitAnimator.cancel() + + animatingExit = true + animationDone = false + +// ambientExitAnimator.doOnEnd { +// animatingExit = false +// } // // interactiveDrawModeUpdateDelayMillis = 16 // animating = true @@ -420,7 +445,7 @@ class WatchCanvasRenderer( // } // } - ambientEnterAnimator.cancel() +// ambientEnterAnimator.cancel() // enteringInteractive = true // enteringAmbient = false @@ -447,8 +472,7 @@ class WatchCanvasRenderer( } } - private lateinit var memoryCache: LruCache - var memInit = false + private val memoryCache: LruCache = LruCache(484) override suspend fun createSharedAssets(): AnalogSharedAssets { return AnalogSharedAssets() @@ -658,10 +682,15 @@ class WatchCanvasRenderer( } } - private fun preloadBitmaps(scale: Float) { + private fun preloadBitmaps(bounds: Rect, scale: Float) { Log.d("WatchCanvasRenderer", "preloadBitmaps($scale)") bitmapsScale = scale + val compBmp = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + memoryCache.put("comp", compBmp) + val canvas = Canvas() preloadHourBitmaps(canvas, scale) preloadHourBitmapsOutline(canvas, scale) @@ -817,7 +846,7 @@ class WatchCanvasRenderer( tmpCanvas.setBitmap(null) - return bmp + return bmp//.asShared() } private fun preloadHourBitmaps(canvas: Canvas, scale: Float) { @@ -1176,6 +1205,8 @@ class WatchCanvasRenderer( private var frame: Int = 0 + private var last: Instant = Instant.now() + override fun render( canvas: Canvas, bounds: Rect, @@ -1187,22 +1218,13 @@ class WatchCanvasRenderer( canvas.drawColor(Color.parseColor("#ff000000")) - if (!memInit) { - // Use 1/8th of the available memory for this memory cache. - val maxItems = 1000 - - memoryCache = LruCache(maxItems) - - memInit = true - } - - if (!this::state.isInitialized || !this::memoryCache.isInitialized) { + if (!this::state.isInitialized) { // placeholder return } if (!bitmapsInitialized || bitmapsScale != renderScale) { - preloadBitmaps(renderScale) + preloadBitmaps(bounds, renderScale) bitmapsInitialized = true } @@ -1228,97 +1250,7 @@ class WatchCanvasRenderer( ) } - var offsetXOld = -// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( -// 35f / 14f * 18f, -// 0f -// ) else if (watchFaceData.detailedAmbient) { -// 0f -// } else { -// 0f -// -// } -// if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) { -// 0f -// } - if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && watchFaceData.ambientStyle != AmbientStyle.DETAILED) interpolate( - 35f / 14f * 18f, - 0f - ) else if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) { - 0f - } else { - 0f - - } -// val offsetX = 0f - - val text14 = Paint(hourPaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57) - textSize = 144f - } - - val text16 = Paint(hourPaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57thin) - textSize = 9f - } - - val text18 = Paint(hourPaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57thick) - textSize = 9f - } - - val hourText = when { - (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f) -> { - Paint(text16) - } - - (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f) -> { - Paint(text18) - } - - else -> { - Paint(text14) - } - } - - val minuteText14 = Paint(minutePaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57) - textSize = 144f - } - - val minuteText16 = Paint(minutePaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57thin) - textSize = 9f - } - - val minuteText18 = Paint(minutePaint).apply { - isAntiAlias = false - typeface = context.resources.getFont(R.font.m8stealth57thick) - textSize = 9f - } - - - val minuteText = when { - (watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f) -> { - Paint(minuteText16) - } - - (watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f) -> { - Paint(minuteText18) - } - - else -> { - Paint(minuteText14) - } - } - canvas.withScale( -// 1f, 1f, timeScale, timeScale, bounds.exactCenterX(), @@ -1338,12 +1270,6 @@ class WatchCanvasRenderer( else -> "normal" } -// val size = if (watchFaceData.bigAmbient && textStyle != "normal") "_big" else "" -// val size = if ( -// (drawProperties.timeScale == 0f) && (drawProperties.timeScale != 0f) && -// (watchFaceData.ambientStyle.id == AmbientStyle.BIG_OUTLINE.id || watchFaceData.ambientStyle.id == AmbientStyle.BIG_BOLD_OUTLINE.id)) -// "_big" else "" - if (true) { val hourText = getHour(zonedDateTime).toString().padStart(2, '0') @@ -1362,7 +1288,6 @@ class WatchCanvasRenderer( ) } - if (true) { val minuteText = zonedDateTime.minute.toString().padStart(2, '0') val cacheKey = "minute_${textStyle}_$minuteText" @@ -1379,62 +1304,21 @@ class WatchCanvasRenderer( Paint().apply { alpha = (opacity * 255).toInt() }, ) } - -// renderTime( -// canvas, -// bounds, -// zonedDateTime, -// hourText, -// minuteText, -// ) - - - -// drawText( -// canvas, -// getHour(zonedDateTime).toString().padStart(2, '0'), -// hourText, -// timeOffsetXThick, -// hourOffsetYThick, -// HOURS_BITMAP_KEY -// ) -// -// drawText( -// canvas, -// zonedDateTime.minute.toString().padStart(2, '0'), -// minuteText, -// timeOffsetXThick, -// minuteOffsetYThick, -// MINUTES_BITMAP_KEY -// ) } } } -// val offsetX = -//// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate( -//// 35f, -//// 0f -//// ) else 0f -// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && watchFaceData.ambientStyle != AmbientStyle.DETAILED) interpolate( -// 35f, -// 0f -// ) else 0f - - - canvas.withScale(complicationsScale, complicationsScale, bounds.exactCenterX(), bounds.exactCenterY()) { canvas.withTranslation(complicationsOffsetX, 0f) { - val compBmp = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) + val compBmp = memoryCache.get("comp") - val compCanvas = Canvas(compBmp) + val compCanvas = Canvas() + compBmp.eraseColor(Color.TRANSPARENT) + compCanvas.setBitmap(compBmp) if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { if (shouldDrawSeconds) { -// val secondText = if (watchFaceData.detailedAmbient && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') val secondText = if (false && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') val cacheKey = "second_normal_$secondText" @@ -1450,19 +1334,6 @@ class WatchCanvasRenderer( Paint(), ) - - - -// drawText( -// compCanvas, -// bounds, -// if (watchFaceData.detailedAmbient && isAmbient) "M8" else zonedDateTime.second.toString() -// .padStart(2, '0'), -// secondPaint, -// secondsOffsetX, -// secondsOffsetY, -// SECONDS_BITMAP_KEY, -// ) } if (shouldDrawAmPm) { @@ -1481,15 +1352,6 @@ class WatchCanvasRenderer( Paint(), ) -// drawText( -// compCanvas, -// bounds, -// getAmPm(zonedDateTime).uppercase(), -// ampmPaint, -// ampmOffsetX, -// 24f, -// AMPM_BITMAP_KEY, -// ) } when (watchFaceData.secondsStyle.id) { @@ -1504,26 +1366,14 @@ class WatchCanvasRenderer( } -// if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && -// (watchFaceData.detailedAmbient || drawProperties.timeScale != 0f) -// ) { -// drawComplications(compCanvas, zonedDateTime) -// } if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && (watchFaceData.ambientStyle == AmbientStyle.DETAILED || drawProperties.timeScale != 0f) ) { drawComplications(compCanvas, zonedDateTime) } -// val scale = if (isAmbient) .75f else 1f -// val scale = interpolate(.75f, 1f) // TODO scaling opacity looks weird due to automatic brightness kicking in -// val opacity = if (watchFaceData.detailedAmbient) scale else interpolate(0f, 1f) -// val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) scale else interpolate(0f, 1f) - val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(0.75f, 1f) else interpolate(0f, 1f) - - canvas.drawBitmap( compBmp, bounds.left.toFloat(), @@ -1536,104 +1386,36 @@ class WatchCanvasRenderer( } - if (debug) { + if (debugTiming) { Log.d("WatchCanvasRenderer", "render took ${took.toFloat() / 1000000.0}ms, cache ${memoryCache.size()} / ${memoryCache.maxSize()}") } - if (!(ambientExitAnimator.isRunning || ambientEnterAnimator.isRunning) && animating) { - animating = false - interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay + if (animatingEnter && !ambientEnterAnimator.isRunning) { + animatingEnter = false } -// if (ambientExitAnimator.isRunning) { -// if (!enteringInteractive) { -// enteringInteractive = true -// } -// } else if (enteringInteractive) { -// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay -// enteringInteractive = false -// } -// -// -// if (ambientEnterAnimator.isRunning) { -// if (!enteringAmbient) { -// enteringAmbient = true -// } -// } else if (enteringAmbient) { -//// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay -// enteringAmbient = false -// } + if (animatingExit && !ambientExitAnimator.isRunning) { + animatingExit = false + } -// if (ambientEnterAnimator.isRunning || ambientExitAnimator.isRunning) { -//// if (!animating) { -//// animating = true -//// interactiveDrawModeUpdateDelayMillis = 16 -//// } -//// interactiveDrawModeUpdateDelayMillis = 16 -//// Log.d("@@@", "set to fast 16") -// } else { -// if (enteringInteractive) { -// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay -// enteringInteractive = false -// } -// -// if (enteringAmbient) { -// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay -// enteringAmbient = false -// } -//// Log.d("@@@", "set to int ${interactiveFrameDelay}") -// } + if ((!animatingEnter && !animatingExit) && !animationDone) { + interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay + animationDone = true + } + +// Log.d("@@@", "Render time diff ${last.until(Instant.now(), ChronoUnit.MICROS)/1000.0f}ms") + last = Instant.now() } override fun shouldAnimate(): Boolean { - return super.shouldAnimate() || animating + return super.shouldAnimate() || animatingEnter || animatingExit } // ----- All drawing functions ----- private fun drawComplications(canvas: Canvas, zonedDateTime: ZonedDateTime) { -// var opacity = -// if (watchFaceData.detailedAmbient) .75f + this.easeInOutCirc(drawProperties.timeScale) / 4 else this.easeInOutCirc( -// drawProperties.timeScale -// ) -// opacity = 1f - -// val offsetX = -// if (watchFaceData.layoutStyle.id == LayoutStyle.SPORT.id && !watchFaceData.detailedAmbient) interpolate(34f, 0f) else 0f -// val offsetX = 0f - -// val maxScale = if (watchFaceData.bigAmbient) 18f / 14f else 16f / 14f -// var scale = if (watchFaceData.detailedAmbient) 1f else interpolate(maxScale, 1f) -// if (watchFaceData.layoutStyle.id == LayoutStyle.FOCUS.id) { -// scale = 1f -// } - for ((_, complication) in complicationSlotsManager.complicationSlots) { if (complication.enabled) { - when (complication.renderer) { - is VerticalComplication -> { -// (complication.renderer as VerticalComplication).opacity = opacity -// (complication.renderer as VerticalComplication).scale = scale -// (complication.renderer as VerticalComplication).debug = debug - } - - is HorizontalComplication -> { -// (complication.renderer as HorizontalComplication).opacity = opacity -// (complication.renderer as HorizontalComplication).offsetX = offsetX -// (complication.renderer as HorizontalComplication).scale = scale -// (complication.renderer as HorizontalComplication).debug = debug - } - - is HorizontalTextComplication -> { -// (complication.renderer as HorizontalTextComplication).opacity = opacity -// (complication.renderer as HorizontalTextComplication).offsetX = offsetX -// (complication.renderer as HorizontalTextComplication).scale = scale -// (complication.renderer as HorizontalTextComplication).debug = debug - } - - else -> {} - } - complication.render(canvas, zonedDateTime, renderParameters) } } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index d98534a..4fdf9d2 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -12,6 +12,7 @@ import android.graphics.RectF import android.graphics.drawable.Drawable import android.os.Build import android.util.Log +import android.util.LruCache import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils @@ -25,7 +26,6 @@ import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.NoDataComplicationData import androidx.wear.watchface.complications.data.ShortTextComplicationData -import dev.rdnt.m8face.BitmapCacheEntry import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime @@ -34,7 +34,7 @@ import kotlin.math.max private const val debug = false class HorizontalComplication(private val context: Context) : CanvasComplication { - private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() + private val memoryCache = LruCache(1) init { Log.d("HorizontalComplication", "Constructor ran") @@ -135,7 +135,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication bounds: Rect, data: ShortTextComplicationData ): Bitmap { - val cached = bitmapCache.get("") + val cached = memoryCache.get("") if (cached != null) { return cached } @@ -149,7 +149,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication renderShortTextComplication(bitmapCanvas, rect, data) - bitmapCache.set("", bitmap) + memoryCache.put("", bitmap) return bitmap } @@ -165,12 +165,12 @@ class HorizontalComplication(private val context: Context) : CanvasComplication p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f - canvas.drawText( - "r ${bitmapCache.loads} w ${bitmapCache.renders}", - bounds.left + 3f, - bounds.bottom - 3f, - p2, - ) +// canvas.drawText( +// "r ${bitmapCache.loads} w ${bitmapCache.renders}", +// bounds.left + 3f, +// bounds.bottom - 3f, +// p2, +// ) } } @@ -253,7 +253,7 @@ class HorizontalComplication(private val context: Context) : CanvasComplication loadDrawablesAsynchronous: Boolean ) { data = complicationData - bitmapCache.set("", null) + memoryCache.remove("") } } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt index 6c92d42..cc24026 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalTextComplication.kt @@ -10,6 +10,7 @@ import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.graphics.RectF import android.util.Log +import android.util.LruCache import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap @@ -31,7 +32,7 @@ import java.time.ZonedDateTime private const val debug = false class HorizontalTextComplication(private val context: Context) : CanvasComplication { - private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() + private val memoryCache = LruCache(1) var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { @@ -39,25 +40,12 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat textPaint.color = tertiaryColor } -// var opacity: Float = 1f -// set(opacity) { -// field = opacity -// -// val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, 1f) -// -// textPaint.color = color -// } - -// var offsetX: Float = 0f - -// var scale: Float = 0f - private val textPaint = Paint().apply { isAntiAlias = true typeface = context.resources.getFont(R.font.m8stealth57) textAlign = Paint.Align.LEFT color = tertiaryColor - textSize = 112f / 14f / 7f + textSize = 112f / 14f / 7f * 14f } override fun render( @@ -69,52 +57,62 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat ) { if (bounds.isEmpty) return + if (memoryCache.get("") == null) { + Log.d("@@@", "create bitmap") + val bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + memoryCache.put("", bitmap) + } - val bitmap: Bitmap + return - when (data.type) { + val bitmap = when (data.type) { ComplicationType.SHORT_TEXT -> { - bitmap = drawShortTextComplication(bounds, data as ShortTextComplicationData) + drawShortTextComplication(bounds, data as ShortTextComplicationData) + } + + ComplicationType.NO_DATA -> { + val placeholder = (data as NoDataComplicationData).placeholder + if (placeholder != null && placeholder.type == ComplicationType.SHORT_TEXT) { + drawShortTextComplication(bounds, placeholder as ShortTextComplicationData) + } else { + return + } } else -> return } -// canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { - renderDebug(canvas, bounds.toRectF()) +// renderDebug(canvas, bounds.toRectF()) canvas.drawBitmap( bitmap, bounds.left.toFloat(), bounds.top.toFloat(), Paint(), -// Paint().apply { alpha = (opacity * 255).toInt() }, ) -// } } private fun drawShortTextComplication( bounds: Rect, data: ShortTextComplicationData ): Bitmap { - val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${debug}" - - // TODO: fix as done in HorizontalComplication -// val cached = bitmapCache.get(hash) -// if (cached != null) { -// return cached -// } - - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) + val cached = memoryCache.get("") + return cached + cached.eraseColor(Color.TRANSPARENT) + val bitmap = cached + +// val bitmap = Bitmap.createBitmap( +// bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 +// ) val bitmapCanvas = Canvas(bitmap) val rect = Rect(0, 0, bitmap.width, bitmap.height) renderShortTextComplication(bitmapCanvas, rect, data) - bitmapCache.set(hash, bitmap) + memoryCache.put("", bitmap) return bitmap } @@ -126,32 +124,16 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat ) { val now = Instant.now() - var text = data.text.getTextAt(context.resources, now).toString().uppercase() - if (text == "--") { - return - } - - val p = Paint(textPaint) - p.textSize *= 14f - -// val textBounds = Rect() -// p.getTextBounds(text, 0, text.length, textBounds) - - - + val text = data.text.getTextAt(context.resources, now).toString().uppercase() val textBounds = Rect() - p.getTextBounds(text, 0, text.length, textBounds) + textPaint.getTextBounds(text, 0, text.length, textBounds) canvas.drawText( text, -// 192f-textBounds.width().toFloat()/2f, -// 192f+textBounds.height()/2+offsetY, -// 0f, -// bounds.height().toFloat()/2f, bounds.left.toFloat() + 14f, bounds.exactCenterY() + textBounds.height() / 2, - p, + textPaint, ) } @@ -166,12 +148,12 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) p2.typeface = context.resources.getFont(R.font.m8stealth57) p2.textSize = 8f - canvas.drawText( - "r ${bitmapCache.loads} w ${bitmapCache.renders}", - bounds.left + 3f, - bounds.bottom - 3f, - p2, - ) +// canvas.drawText( +// "r ${bitmapCache.loads} w ${bitmapCache.renders}", +// bounds.left + 3f, +// bounds.bottom - 3f, +// p2, +// ) } } @@ -194,6 +176,7 @@ class HorizontalTextComplication(private val context: Context) : CanvasComplicat loadDrawablesAsynchronous: Boolean ) { data = complicationData +// memoryCache.remove("") } } From 72fbcc5b4cc13fd98c5e3e93d9f99cf058ac6cd1 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 27 Mar 2024 17:24:16 +0200 Subject: [PATCH 47/52] single animator --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 128 ++++++++++++------ 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 5ea40c1..d7cae93 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -30,6 +30,7 @@ import android.util.LruCache import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep +import androidx.compose.runtime.saveable.autoSaver import androidx.core.animation.doOnEnd import androidx.core.graphics.ColorUtils import androidx.core.graphics.set @@ -271,7 +272,7 @@ class WatchCanvasRenderer( // private var enteringAmbientFrame = 0 // private var enteringInteractiveFrame = 0 - private val ambientExitAnimator = + private val ambientExitAnimatorOld = AnimatorSet().apply { play( ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1.0f).apply { @@ -286,7 +287,8 @@ class WatchCanvasRenderer( } // Animation played when entering ambient mode. - private val ambientEnterAnimator = + + private val ambientEnterAnimatorOld = AnimatorSet().apply { // interpolator = AnimationUtils.loadInterpolator( // context, @@ -302,20 +304,62 @@ class WatchCanvasRenderer( // DrawProperties.TIME_SCALE, *keyframes, Keyframe.ofFloat(1f, 0f) // ) - playSequentially( - ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale).apply { - setAutoCancel(true) - }, - ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 0.0f).apply { + play( +// ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale).apply { +// +// duration = ambientTransitionMs/4 +// }, + ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1f).apply { interpolator = AnimationUtils.loadInterpolator( context, android.R.interpolator.accelerate_decelerate ) - setAutoCancel(true) +// setAutoCancel(true) +// duration = ambientTransitionMs }, ) } + private val ambientAnimator = +// AnimatorSet().apply { +// interpolator = AnimationUtils.loadInterpolator( +// context, +// android.R.interpolator.decelerate_cubic +// ) + +// val keyframes = arrayOf( +// Keyframe.ofFloat(0f, 1f), +// Keyframe.ofFloat(1f, 0f), +// ) +// +// val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( +// DrawProperties.TIME_SCALE, *keyframes, Keyframe.ofFloat(1f, 0f) +// ) + +// play( +// ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale).apply { +// setAutoCancel(true) +// }, + ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1f).apply { + interpolator = AnimationUtils.loadInterpolator( + context, + android.R.interpolator.accelerate_decelerate + ) + duration = ambientTransitionMs + setAutoCancel(true) + } +// ) +// } + +// val animator = ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1.0f).apply { +// interpolator = AnimationUtils.loadInterpolator( +// context, +// android.R.interpolator.accelerate_decelerate +// ) +// setAutoCancel(true) +// } + + // override fun onRenderParametersChanged(renderParameters: RenderParameters) { // super.onRenderParametersChanged(renderParameters) // @@ -323,9 +367,9 @@ class WatchCanvasRenderer( // } // private var animating = false - private var animatingEnter = false - private var animatingExit = false - private var animationDone = false + private var animatingEnterOld = false + private var animatingExitOld = false + private var animating = false private lateinit var state: UserStyle @@ -363,11 +407,6 @@ class WatchCanvasRenderer( // Log.d("@@@", "cancel before enter") - ambientEnterAnimator.removeAllListeners() - ambientExitAnimator.removeAllListeners() -// - ambientEnterAnimator.cancel() - ambientExitAnimator.cancel() // // interactiveDrawModeUpdateDelayMillis = 16 @@ -398,33 +437,42 @@ class WatchCanvasRenderer( // Log.d("@@@", ambientTransitionMs.toString()) - animatingEnter = true - animationDone = false + ambientAnimator.cancel() + animating = true + ambientAnimator.setFloatValues(0f) + ambientAnimator.duration = ((drawProperties.timeScale) * (ambientTransitionMs*2).toFloat()).toLong() + ambientAnimator.start() // ambientEnterAnimator.doOnEnd { // animatingEnter = false // } - ambientEnterAnimator.setupStartValues() +// ambientAnimator.setupStartValues() // ambientEnterAnimator.duration = (drawProperties.timeScale * ambientTransitionMs.toFloat()).toLong() - ambientEnterAnimator.childAnimations[0].duration = (drawProperties.timeScale * (ambientTransitionMs/2).toFloat()).toLong() - ambientEnterAnimator.childAnimations[1].duration = (drawProperties.timeScale * (ambientTransitionMs/2*3).toFloat()).toLong() +// ambientAnimator.childAnimations[0].duration = (drawProperties.timeScale * (ambientTransitionMs/2).toFloat()).toLong() +// ambientAnimator.childAnimations[1].duration = (drawProperties.timeScale * (ambientTransitionMs/2*3).toFloat()).toLong() // interactiveDrawModeUpdateDelayMillis = 16 - ambientEnterAnimator.start() + +// ambientAnimator.setDuration(ambientTransitionMs*2) +// ambientAnimator.start() + } else { interactiveDrawModeUpdateDelayMillis = 16 // Log.d("@@@", "ambient off") // interactiveDrawModeUpdateDelayMillis = 16 // Log.d("@@@", "cancel before exit") - ambientEnterAnimator.removeAllListeners() - ambientExitAnimator.removeAllListeners() -// - ambientEnterAnimator.cancel() - ambientExitAnimator.cancel() +// ambientEnterAnimator.removeAllListeners() +// ambientExitAnimator.removeAllListeners() +//// +// ambientEnterAnimator.cancel() +// ambientExitAnimator.cancel() - animatingExit = true - animationDone = false + ambientAnimator.cancel() + animating = true + ambientAnimator.setFloatValues(1f) + ambientAnimator.duration = ((1f-drawProperties.timeScale) * (ambientTransitionMs).toFloat()).toLong() + ambientAnimator.start() // ambientExitAnimator.doOnEnd { // animatingExit = false @@ -458,12 +506,14 @@ class WatchCanvasRenderer( // } // Log.d("@@@", "starting from ${(1f-drawProperties.timeScale) * AMBIENT_TRANSITION_MS.toFloat()} / $AMBIENT_TRANSITION_MS") - ambientExitAnimator.setupStartValues() +// ambientAnimator.setupStartValues() // ambientExitAnimator.currentPlayTime = ((1f-drawProperties.timeScale) * AMBIENT_TRANSITION_MS.toFloat()).toLong() - ambientExitAnimator.duration = ((1f-drawProperties.timeScale) * ambientTransitionMs.toFloat()).toLong() +// ambientAnimator.duration = ((drawProperties.timeScale) * ambientTransitionMs.toFloat()).toLong() +// ambientAnimator.childAnimations[1].duration = ((1f-drawProperties.timeScale) * ambientTransitionMs.toFloat()).toLong() // interactiveDrawModeUpdateDelayMillis = 16 - ambientExitAnimator.start() +// ambientAnimator.setDuration(ambientTransitionMs) +// ambientAnimator.start() } } else { drawProperties.timeScale = 1f @@ -1390,17 +1440,9 @@ class WatchCanvasRenderer( Log.d("WatchCanvasRenderer", "render took ${took.toFloat() / 1000000.0}ms, cache ${memoryCache.size()} / ${memoryCache.maxSize()}") } - if (animatingEnter && !ambientEnterAnimator.isRunning) { - animatingEnter = false - } - - if (animatingExit && !ambientExitAnimator.isRunning) { - animatingExit = false - } - - if ((!animatingEnter && !animatingExit) && !animationDone) { + if (animating && !ambientAnimator.isRunning) { + animating = false interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay - animationDone = true } // Log.d("@@@", "Render time diff ${last.until(Instant.now(), ChronoUnit.MICROS)/1000.0f}ms") @@ -1409,7 +1451,7 @@ class WatchCanvasRenderer( } override fun shouldAnimate(): Boolean { - return super.shouldAnimate() || animatingEnter || animatingExit + return super.shouldAnimate() || animating } // ----- All drawing functions ----- From 9bee4822d9b74772be3daee974f9e7e59dc4fb0f Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 27 Mar 2024 17:46:39 +0200 Subject: [PATCH 48/52] before cleanup, broken horizontal compl --- .../main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index d7cae93..affa30f 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -19,6 +19,7 @@ import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.content.Context import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint @@ -31,8 +32,11 @@ import android.view.SurfaceHolder import android.view.animation.AnimationUtils import androidx.annotation.Keep import androidx.compose.runtime.saveable.autoSaver +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.asImageBitmap import androidx.core.animation.doOnEnd import androidx.core.graphics.ColorUtils +import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.set import androidx.core.graphics.withRotation import androidx.core.graphics.withScale @@ -1263,16 +1267,15 @@ class WatchCanvasRenderer( zonedDateTime: ZonedDateTime, sharedAssets: AnalogSharedAssets, ) { - // all calculations are done with 384x384 resolution in mind - val renderScale = min(bounds.width(), bounds.height()).toFloat() / 384.0f - canvas.drawColor(Color.parseColor("#ff000000")) if (!this::state.isInitialized) { - // placeholder return } + // all calculations are done with 384x384 resolution in mind + val renderScale = min(bounds.width(), bounds.height()).toFloat() / 384.0f + if (!bitmapsInitialized || bitmapsScale != renderScale) { preloadBitmaps(bounds, renderScale) bitmapsInitialized = true From e529af6874ebeb9619fc56d87de5ab75122af358 Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Wed, 27 Mar 2024 17:48:20 +0200 Subject: [PATCH 49/52] 3.0.2-alpha --- CHANGELOG.md | 3 +++ app/build.gradle | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 016f939..1e41bc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ Last updated: 2024-03-25 +- v3.0.2-alpha + - Use single animator to help avoid data races/bugs + - v3.0.1-alpha - Use LruCache instead of BitmapCache diff --git a/app/build.gradle b/app/build.gradle index af65f52..168ce7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { applicationId "dev.rdnt.m8face" minSdk 28 targetSdk 33 - versionCode 58 - versionName '3.0.1' + versionCode 59 + versionName '3.0.2' } buildFeatures { From eb645b46069f1da70755031007c32afe0fbe761c Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Thu, 28 Mar 2024 17:14:14 +0200 Subject: [PATCH 50/52] Refine complications/seconds drawing, use only enter animator, cleanup & bug fixes --- app/build.gradle | 4 +- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 475 +++++------------- gradle/libs.versions.toml | 19 +- 3 files changed, 127 insertions(+), 371 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 168ce7c..2a02e41 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { applicationId "dev.rdnt.m8face" minSdk 28 targetSdk 33 - versionCode 59 - versionName '3.0.2' + versionCode 62 + versionName '3.0.5' } buildFeatures { diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index affa30f..3bace67 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -35,6 +35,7 @@ import androidx.compose.runtime.saveable.autoSaver import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.core.animation.doOnEnd +import androidx.core.animation.doOnStart import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.set @@ -263,134 +264,35 @@ class WatchCanvasRenderer( private var is24Format: Boolean = watchFaceData.militaryTime - private var drawProperties = DrawProperties() // timeScale = 0f + private var drawProperties = DrawProperties() private var isHeadless = false private var isAmbient = false private var interactiveFrameDelay: Long = 16 -// private var animating: Boolean = false - - private var enteringAmbient = false - private var enteringInteractive = false -// private var enteringAmbientFrame = 0 -// private var enteringInteractiveFrame = 0 - - private val ambientExitAnimatorOld = - AnimatorSet().apply { - play( - ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1.0f).apply { - interpolator = - AnimationUtils.loadInterpolator( - context, - android.R.interpolator.accelerate_decelerate - ) - setAutoCancel(true) - }, - ) - } - - // Animation played when entering ambient mode. - - private val ambientEnterAnimatorOld = - AnimatorSet().apply { -// interpolator = AnimationUtils.loadInterpolator( -// context, -// android.R.interpolator.decelerate_cubic -// ) - -// val keyframes = arrayOf( -// Keyframe.ofFloat(0f, 1f), -// Keyframe.ofFloat(1f, 0f), -// ) -// -// val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( -// DrawProperties.TIME_SCALE, *keyframes, Keyframe.ofFloat(1f, 0f) -// ) - - play( -// ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale).apply { -// -// duration = ambientTransitionMs/4 -// }, - ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1f).apply { - interpolator = AnimationUtils.loadInterpolator( - context, - android.R.interpolator.accelerate_decelerate - ) -// setAutoCancel(true) -// duration = ambientTransitionMs - }, + private val ambientAnimator = + ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 0f, 1f).apply { + interpolator = AnimationUtils.loadInterpolator( + context, + android.R.interpolator.accelerate_decelerate ) + duration = ambientTransitionMs + setAutoCancel(true) } - private val ambientAnimator = -// AnimatorSet().apply { -// interpolator = AnimationUtils.loadInterpolator( -// context, -// android.R.interpolator.decelerate_cubic -// ) - -// val keyframes = arrayOf( -// Keyframe.ofFloat(0f, 1f), -// Keyframe.ofFloat(1f, 0f), -// ) -// -// val propertyValuesHolder = PropertyValuesHolder.ofKeyframe( -// DrawProperties.TIME_SCALE, *keyframes, Keyframe.ofFloat(1f, 0f) -// ) - -// play( -// ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, drawProperties.timeScale).apply { -// setAutoCancel(true) -// }, - ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1f).apply { - interpolator = AnimationUtils.loadInterpolator( - context, - android.R.interpolator.accelerate_decelerate - ) - duration = ambientTransitionMs - setAutoCancel(true) - } -// ) -// } - -// val animator = ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 1.0f).apply { -// interpolator = AnimationUtils.loadInterpolator( -// context, -// android.R.interpolator.accelerate_decelerate -// ) -// setAutoCancel(true) -// } - - -// override fun onRenderParametersChanged(renderParameters: RenderParameters) { -// super.onRenderParametersChanged(renderParameters) -// -// preloadBitmaps() -// } - -// private var animating = false - private var animatingEnterOld = false - private var animatingExitOld = false - private var animating = false - private lateinit var state: UserStyle - var bitmapsInitialized: Boolean = false - var bitmapsScale: Float = 0f + private var bitmapsInitialized: Boolean = false + private var bitmapsScale: Float = 0f override suspend fun init() { super.init() -// preloadBitmaps() } init { scope.launch { currentUserStyleRepository.userStyle.collect { userStyle -> updateWatchFaceData(userStyle) - bitmapsInitialized = false -// preloadBitmaps(bounds.width()) state = userStyle } } @@ -401,123 +303,17 @@ class WatchCanvasRenderer( isAmbient = ambient!! if (!watchState.isHeadless) { -// animating = true - if (isAmbient) { -// Log.d("@@@", "ambient on") -// drawProperties.timeScale = 0f -// ambientEnterAnimator.setupStartValues() - interactiveDrawModeUpdateDelayMillis = 33 - - -// Log.d("@@@", "cancel before enter") -// -// interactiveDrawModeUpdateDelayMillis = 16 - -// ambientEnterAnimator.apply { -// doOnStart { -//// Log.d("@@@", "enter start") -//// interactiveDrawModeUpdateDelayMillis = 16 -// } -// -// doOnEnd { -//// Log.d("@@@", "enter end") -// -//// animating = false -//// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay -//// interactiveDrawModeUpdateDelayMillis = 16 -// } -// } -// ambientExitAnimator.cancel() - -// enteringInteractive = false -// enteringAmbient = true -// enteringAmbientFrame = 1 -// interactiveDrawModeUpdateDelayMillis = 16 - -// if (!animating) { -// animating = true -// } - -// Log.d("@@@", ambientTransitionMs.toString()) - + ambientAnimator.removeAllListeners() ambientAnimator.cancel() - animating = true - ambientAnimator.setFloatValues(0f) - ambientAnimator.duration = ((drawProperties.timeScale) * (ambientTransitionMs*2).toFloat()).toLong() - ambientAnimator.start() - -// ambientEnterAnimator.doOnEnd { -// animatingEnter = false -// } - -// ambientAnimator.setupStartValues() -// ambientEnterAnimator.duration = (drawProperties.timeScale * ambientTransitionMs.toFloat()).toLong() -// ambientAnimator.childAnimations[0].duration = (drawProperties.timeScale * (ambientTransitionMs/2).toFloat()).toLong() -// ambientAnimator.childAnimations[1].duration = (drawProperties.timeScale * (ambientTransitionMs/2*3).toFloat()).toLong() -// interactiveDrawModeUpdateDelayMillis = 16 - -// ambientAnimator.setDuration(ambientTransitionMs*2) -// ambientAnimator.start() - + drawProperties.timeScale = 0f } else { interactiveDrawModeUpdateDelayMillis = 16 -// Log.d("@@@", "ambient off") -// interactiveDrawModeUpdateDelayMillis = 16 -// Log.d("@@@", "cancel before exit") - -// ambientEnterAnimator.removeAllListeners() -// ambientExitAnimator.removeAllListeners() -//// -// ambientEnterAnimator.cancel() -// ambientExitAnimator.cancel() + ambientAnimator.removeAllListeners() ambientAnimator.cancel() - animating = true - ambientAnimator.setFloatValues(1f) - ambientAnimator.duration = ((1f-drawProperties.timeScale) * (ambientTransitionMs).toFloat()).toLong() + ambientAnimator.doOnEnd { interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay } ambientAnimator.start() - -// ambientExitAnimator.doOnEnd { -// animatingExit = false -// } -// -// interactiveDrawModeUpdateDelayMillis = 16 -// animating = true - -// ambientExitAnimator.apply { -// doOnStart { -//// Log.d("@@@", "exit start") -//// animating = true -// } -// doOnEnd { -//// Log.d("@@@", "exit end") -//// animating = false -//// interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay -// } -// } - -// ambientEnterAnimator.cancel() - -// enteringInteractive = true -// enteringAmbient = false -// interactiveDrawModeUpdateDelayMillis = 16 -// enteringInteractiveFrame = 1 - -// if (!animating) { -// animating = true -// interactiveDrawModeUpdateDelayMillis = 16 -// } - -// Log.d("@@@", "starting from ${(1f-drawProperties.timeScale) * AMBIENT_TRANSITION_MS.toFloat()} / $AMBIENT_TRANSITION_MS") -// ambientAnimator.setupStartValues() -// ambientExitAnimator.currentPlayTime = ((1f-drawProperties.timeScale) * AMBIENT_TRANSITION_MS.toFloat()).toLong() - -// ambientAnimator.duration = ((drawProperties.timeScale) * ambientTransitionMs.toFloat()).toLong() -// ambientAnimator.childAnimations[1].duration = ((1f-drawProperties.timeScale) * ambientTransitionMs.toFloat()).toLong() -// interactiveDrawModeUpdateDelayMillis = 16 -// ambientAnimator.setDuration(ambientTransitionMs) -// ambientAnimator.start() } } else { drawProperties.timeScale = 1f @@ -526,23 +322,12 @@ class WatchCanvasRenderer( } } - private val memoryCache: LruCache = LruCache(484) + private val memoryCache: LruCache = LruCache(485) override suspend fun createSharedAssets(): AnalogSharedAssets { return AnalogSharedAssets() } -// private fun updateRefreshRate() { -// interactiveDrawModeUpdateDelayMillis = when { -// ambientEnterAnimator.isRunning || ambientExitAnimator.isRunning -> 19 // TODO // 60 fps cause animating -//// watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 60000 // update once a second -// watchFaceData.secondsStyle.id == SecondsStyle.NONE.id -> 1000 // update once a second // TODO: handle digital seconds -// watchFaceData.secondsStyle.id == SecondsStyle.DASHES.id -> 17 // 60 fps TODO -// watchFaceData.secondsStyle.id == SecondsStyle.DOTS.id -> 18 // 60 fps TODO -// else -> 60000 // safe default -// } -// } - /* * Triggered when the user makes changes to the watch face through the settings activity. The * function is called by a flow. @@ -745,6 +530,11 @@ class WatchCanvasRenderer( ) memoryCache.put("comp", compBmp) + val secsBmp = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + memoryCache.put("secs", secsBmp) + val canvas = Canvas() preloadHourBitmaps(canvas, scale) preloadHourBitmapsOutline(canvas, scale) @@ -1087,21 +877,21 @@ class WatchCanvasRenderer( LayoutStyle.INFO1.id, LayoutStyle.INFO2.id, LayoutStyle.INFO3.id, LayoutStyle.INFO4.id, LayoutStyle.SPORT.id -> { when(watchFaceData.ambientStyle) { AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { - if (drawProperties.timeScale == 0f) { + if (isAmbient) { 18f / 18f } else { interpolate(16f / 18f, 14f / 18f) } } AmbientStyle.BIG_OUTLINE, AmbientStyle.BIG_BOLD_OUTLINE, AmbientStyle.BIG_FILLED -> { - if (drawProperties.timeScale == 0f) { + if (isAmbient) { 18f / 18f } else { interpolate(18f / 18f, 14f / 18f) } } AmbientStyle.FILLED -> { - if (drawProperties.timeScale == 0f) { + if (isAmbient) { 16f / 18f } else { interpolate(16f / 18f, 14f / 18f) @@ -1114,14 +904,14 @@ class WatchCanvasRenderer( LayoutStyle.FOCUS.id -> { when(watchFaceData.ambientStyle) { AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { - if (drawProperties.timeScale == 0f) { + if (isAmbient) { 18f / 18f } else { 16f / 18f } } AmbientStyle.BIG_OUTLINE, AmbientStyle.BIG_BOLD_OUTLINE, AmbientStyle.BIG_FILLED -> { - if (drawProperties.timeScale == 0f) { + if (isAmbient) { 18f / 18f } else { interpolate(18f / 18f, 16f / 18f) @@ -1168,7 +958,7 @@ class WatchCanvasRenderer( val hourOffsetY: Float get() = when (watchFaceData.ambientStyle) { AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { - if (drawProperties.timeScale == 0f) { + if (isAmbient) { -64f } else { -72f @@ -1181,7 +971,7 @@ class WatchCanvasRenderer( val minuteOffsetY: Float get() = when (watchFaceData.ambientStyle) { AmbientStyle.OUTLINE, AmbientStyle.BOLD_OUTLINE -> { - if (drawProperties.timeScale == 0f) { + if (isAmbient) { 64f } else { 72f @@ -1215,6 +1005,15 @@ class WatchCanvasRenderer( else -> false } + val timeTextStyle: String + get() = when (watchFaceData.ambientStyle.id) { + AmbientStyle.OUTLINE.id -> "outline" + AmbientStyle.BIG_OUTLINE.id -> "big_outline" + AmbientStyle.BOLD_OUTLINE.id -> "bold_outline" + AmbientStyle.BIG_BOLD_OUTLINE.id -> "big_bold_outline" + else -> "normal" + } + val timeOffsetX: Float get() = when (watchFaceData.layoutStyle.id) { LayoutStyle.SPORT.id -> { @@ -1257,10 +1056,6 @@ class WatchCanvasRenderer( } } - private var frame: Int = 0 - - private var last: Instant = Instant.now() - override fun render( canvas: Canvas, bounds: Rect, @@ -1269,9 +1064,9 @@ class WatchCanvasRenderer( ) { canvas.drawColor(Color.parseColor("#ff000000")) - if (!this::state.isInitialized) { - return - } +// if (!this::state.isInitialized) { +// return +// } // all calculations are done with 384x384 resolution in mind val renderScale = min(bounds.width(), bounds.height()).toFloat() / 384.0f @@ -1282,27 +1077,6 @@ class WatchCanvasRenderer( } val took = measureNanoTime { - frame++ - - if (debug) { - val p2 = Paint() - p2.color = Color.parseColor("#aaff1111") - p2.typeface = context.resources.getFont(R.font.m8stealth57) - p2.textSize = 8f - - val text = "f $frame r $interactiveDrawModeUpdateDelayMillis s ${"%.2f".format(drawProperties.timeScale)}" - - val textBounds = Rect() - p2.getTextBounds(text, 0, text.length, textBounds) - - canvas.drawText( - text, - 192f - textBounds.width() / 2, - 192f + textBounds.height() / 2, - p2, - ) - } - canvas.withScale( timeScale, timeScale, @@ -1313,66 +1087,48 @@ class WatchCanvasRenderer( canvas.withTranslation(timeOffsetX * renderScale, 0f) { if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - val opacity = interpolate(0.75f, 1f) - - var textStyle = when { - watchFaceData.ambientStyle.id == AmbientStyle.OUTLINE.id && drawProperties.timeScale == 0f -> "outline" - watchFaceData.ambientStyle.id == AmbientStyle.BIG_OUTLINE.id && drawProperties.timeScale == 0f -> "big_outline" - watchFaceData.ambientStyle.id == AmbientStyle.BOLD_OUTLINE.id && drawProperties.timeScale == 0f -> "bold_outline" - watchFaceData.ambientStyle.id == AmbientStyle.BIG_BOLD_OUTLINE.id && drawProperties.timeScale == 0f -> "big_bold_outline" - else -> "normal" - } - - if (true) { - val hourText = getHour(zonedDateTime).toString().padStart(2, '0') - - val cacheKey = "hour_${textStyle}_$hourText" - - val hourBmp = memoryCache.get(cacheKey) + val opacity = interpolate(1f, 1f) - val hourOffsetX = 0f - val hourOffsetY = hourOffsetY * renderScale + val hourText = getHour(zonedDateTime).toString().padStart(2, '0') + val hourBmp = memoryCache.get("hour_${timeTextStyle}_$hourText") - canvas.drawBitmap( - hourBmp, - bounds.exactCenterX() - hourBmp.width / 2f + hourOffsetX, - bounds.exactCenterY() - hourBmp.height / 2f + hourOffsetY, - Paint().apply { alpha = (opacity * 255).toInt() }, - ) - } - - if (true) { - val minuteText = zonedDateTime.minute.toString().padStart(2, '0') - val cacheKey = "minute_${textStyle}_$minuteText" - - val minuteBmp = memoryCache.get(cacheKey) + canvas.drawBitmap( + hourBmp, + bounds.exactCenterX() - hourBmp.width / 2f, + bounds.exactCenterY() - hourBmp.height / 2f + hourOffsetY * renderScale, + Paint().apply { alpha = (opacity * 255).toInt() }, + ) - val minuteOffsetX = 0f - val minuteOffsetY = minuteOffsetY * renderScale + val minuteText = zonedDateTime.minute.toString().padStart(2, '0') + val minuteBmp = memoryCache.get("minute_${timeTextStyle}_$minuteText") - canvas.drawBitmap( - minuteBmp, - bounds.exactCenterX() - minuteBmp.width / 2f + minuteOffsetX, - bounds.exactCenterY() - minuteBmp.height / 2f + minuteOffsetY, - Paint().apply { alpha = (opacity * 255).toInt() }, - ) - } + canvas.drawBitmap( + minuteBmp, + bounds.exactCenterX() - minuteBmp.width / 2f, + bounds.exactCenterY() - minuteBmp.height / 2f + minuteOffsetY * renderScale, + Paint().apply { alpha = (opacity * 255).toInt() }, + ) } } } - canvas.withScale(complicationsScale, complicationsScale, bounds.exactCenterX(), bounds.exactCenterY()) { - canvas.withTranslation(complicationsOffsetX, 0f) { + val shouldDrawComplications = renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && + (!isAmbient || watchFaceData.ambientStyle == AmbientStyle.DETAILED) + val shouldDrawSecondsAmPm = renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE) - val compBmp = memoryCache.get("comp") + if (shouldDrawComplications || shouldDrawSecondsAmPm) { + val compBmp = memoryCache.get("comp") + compBmp.eraseColor(Color.TRANSPARENT) + val compCanvas = Canvas(compBmp) - val compCanvas = Canvas() - compBmp.eraseColor(Color.TRANSPARENT) - compCanvas.setBitmap(compBmp) + val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(1f, 1f) else interpolate(0f, 1f) - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.BASE)) { - if (shouldDrawSeconds) { - val secondText = if (false && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') + canvas.withScale(complicationsScale, complicationsScale, bounds.exactCenterX(), bounds.exactCenterY()) { + canvas.withTranslation(complicationsOffsetX, 0f) { + + if (shouldDrawSecondsAmPm) { + if (shouldDrawSeconds) { + val secondText = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED && isAmbient) "M8" else zonedDateTime.second.toString().padStart(2, '0') val cacheKey = "second_normal_$secondText" val secondBmp = memoryCache.get(cacheKey) @@ -1387,74 +1143,75 @@ class WatchCanvasRenderer( Paint(), ) - } - - if (shouldDrawAmPm) { - val ampmText = getAmPm(zonedDateTime).uppercase() - val cacheKey = "ampm_$ampmText" + } - val ampmBmp = memoryCache.get(cacheKey) + if (shouldDrawAmPm) { + val ampmText = getAmPm(zonedDateTime).uppercase() + val cacheKey = "ampm_$ampmText" - val offsetX = 83f * renderScale - val offsetY = 24f * renderScale + val ampmBmp = memoryCache.get(cacheKey) - compCanvas.drawBitmap( - ampmBmp, - bounds.exactCenterX() - ampmBmp.width / 2 + offsetX, - bounds.exactCenterY() - ampmBmp.height / 2 + offsetY, - Paint(), - ) + val offsetX = 83f * renderScale + val offsetY = 24f * renderScale - } + compCanvas.drawBitmap( + ampmBmp, + bounds.exactCenterX() - ampmBmp.width / 2 + offsetX, + bounds.exactCenterY() - ampmBmp.height / 2 + offsetY, + Paint(), + ) - when (watchFaceData.secondsStyle.id) { - SecondsStyle.DASHES.id -> { - drawDashes(compCanvas, bounds, zonedDateTime) } + } - SecondsStyle.DOTS.id -> { - drawDots(compCanvas, bounds, zonedDateTime) - } + if (shouldDrawComplications) { + drawComplications(compCanvas, zonedDateTime) } - } + canvas.drawBitmap( + compBmp, + bounds.left.toFloat(), + bounds.top.toFloat(), + Paint().apply { alpha = (opacity * 255).toInt() }, + ) - if (renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS) && - (watchFaceData.ambientStyle == AmbientStyle.DETAILED || drawProperties.timeScale != 0f) - ) { - drawComplications(compCanvas, zonedDateTime) } + } + } - val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(0.75f, 1f) else interpolate(0f, 1f) + if (watchFaceData.secondsStyle != SecondsStyle.NONE) { + val secsBmp = memoryCache.get("secs") + secsBmp.eraseColor(Color.TRANSPARENT) + val secsCanvas = Canvas(secsBmp) - canvas.drawBitmap( - compBmp, - bounds.left.toFloat(), - bounds.top.toFloat(), - Paint().apply { alpha = (opacity * 255).toInt() }, - ) + val opacity = if (watchFaceData.ambientStyle == AmbientStyle.DETAILED) interpolate(1f, 1f) else interpolate(0f, 1f) + when (watchFaceData.secondsStyle.id) { + SecondsStyle.DASHES.id -> { + drawDashes(secsCanvas, bounds, zonedDateTime) + } + + SecondsStyle.DOTS.id -> { + drawDots(secsCanvas, bounds, zonedDateTime) + } } - } + canvas.drawBitmap( + secsBmp, + bounds.left.toFloat(), + bounds.top.toFloat(), + Paint().apply { alpha = (opacity * 255).toInt() }, + ) + } } if (debugTiming) { Log.d("WatchCanvasRenderer", "render took ${took.toFloat() / 1000000.0}ms, cache ${memoryCache.size()} / ${memoryCache.maxSize()}") } - - if (animating && !ambientAnimator.isRunning) { - animating = false - interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay - } - -// Log.d("@@@", "Render time diff ${last.until(Instant.now(), ChronoUnit.MICROS)/1000.0f}ms") - last = Instant.now() - } override fun shouldAnimate(): Boolean { - return super.shouldAnimate() || animating + return super.shouldAnimate() || ambientAnimator.isRunning } // ----- All drawing functions ----- diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0c5c480..94174ba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,11 @@ [versions] accompanist-pager = "0.32.0" -activity-compose = "1.8.0" -android-gradle-plugin = "8.3.0-rc02" -androidx-activity = "1.8.0" -androidx-lifecycle = "2.6.2" -androidx-wear-watchface = "1.1.1" -compiler = "1.5.3" -compose-material = "1.2.0" +activity-compose = "1.8.2" +android-gradle-plugin = "8.3.1" +androidx-activity = "1.8.2" +androidx-lifecycle = "2.7.0" +androidx-wear-watchface = "1.2.1" +compiler = "1.5.11" horologist-compose-layout = "0.5.7" ktlint = "0.46.1" org-jetbrains-kotlin = "1.9.10" @@ -15,14 +14,14 @@ org-jetbrains-kotlinx = "1.7.3" [libraries] accompanist-pager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist-pager" } accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanist-pager" } -android-material = "com.google.android.material:material:1.10.0" +android-material = "com.google.android.material:material:1.11.0" androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" } androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } androidx-compose-material-iconsExtended = { module = "androidx.compose.material:material-icons-extended" } androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" } androidx-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compiler" } -androidx-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version = "1.3.0-alpha07" } -androidx-compose-material = { module = "androidx.wear.compose:compose-material", version = "1.3.0-alpha07" } +androidx-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version = "1.3.0" } +androidx-compose-material = { module = "androidx.wear.compose:compose-material", version = "1.3.0" } androidx-core-ktx = "androidx.core:core-ktx:1.12.0" androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } From 55b966a07753491c71c649112fd1a1bd653909ab Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Thu, 28 Mar 2024 17:50:10 +0200 Subject: [PATCH 51/52] fix textstyle --- .../java/dev/rdnt/m8face/WatchCanvasRenderer.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 3bace67..53eef4e 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -1006,12 +1006,17 @@ class WatchCanvasRenderer( } val timeTextStyle: String - get() = when (watchFaceData.ambientStyle.id) { - AmbientStyle.OUTLINE.id -> "outline" - AmbientStyle.BIG_OUTLINE.id -> "big_outline" - AmbientStyle.BOLD_OUTLINE.id -> "bold_outline" - AmbientStyle.BIG_BOLD_OUTLINE.id -> "big_bold_outline" - else -> "normal" + get() = when(isAmbient) { + true -> { + when (watchFaceData.ambientStyle.id) { + AmbientStyle.OUTLINE.id -> "outline" + AmbientStyle.BIG_OUTLINE.id -> "big_outline" + AmbientStyle.BOLD_OUTLINE.id -> "bold_outline" + AmbientStyle.BIG_BOLD_OUTLINE.id -> "big_bold_outline" + else -> "normal" + } + } + false -> "normal" } val timeOffsetX: Float From 4ffbc7a2effaa177073ab4c0dced3fb53be7493b Mon Sep 17 00:00:00 2001 From: Tasos Papalyras Date: Thu, 28 Mar 2024 21:23:08 +0200 Subject: [PATCH 52/52] cache-optimized complication renderer --- .../dev/rdnt/m8face/WatchCanvasRenderer.kt | 23 +-- .../rdnt/m8face/utils/ComplicationUtils.kt | 53 ++++++ .../m8face/utils/HorizontalComplication.kt | 101 ++--------- .../rdnt/m8face/utils/VerticalComplication.kt | 166 ++---------------- 4 files changed, 96 insertions(+), 247 deletions(-) diff --git a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt index 53eef4e..a136fe2 100644 --- a/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt +++ b/app/src/main/java/dev/rdnt/m8face/WatchCanvasRenderer.kt @@ -268,8 +268,6 @@ class WatchCanvasRenderer( private var isHeadless = false private var isAmbient = false - private var interactiveFrameDelay: Long = 16 - private val ambientAnimator = ObjectAnimator.ofFloat(drawProperties, DrawProperties.TIME_SCALE, 0f, 1f).apply { interpolator = AnimationUtils.loadInterpolator( @@ -307,6 +305,7 @@ class WatchCanvasRenderer( ambientAnimator.removeAllListeners() ambientAnimator.cancel() drawProperties.timeScale = 0f + interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay } else { interactiveDrawModeUpdateDelayMillis = 16 @@ -322,6 +321,14 @@ class WatchCanvasRenderer( } } + private val interactiveFrameDelay: Long + get() = when (watchFaceData.secondsStyle.id) { + SecondsStyle.NONE.id -> if (shouldDrawSeconds) 1000 else 60000 + SecondsStyle.DASHES.id -> 16 + SecondsStyle.DOTS.id -> 16 + else -> 1000 + } + private val memoryCache: LruCache = LruCache(485) override suspend fun createSharedAssets(): AnalogSharedAssets { @@ -490,13 +497,9 @@ class WatchCanvasRenderer( is24Format = watchFaceData.militaryTime - interactiveFrameDelay = when (watchFaceData.secondsStyle.id) { - SecondsStyle.NONE.id -> if (shouldDrawSeconds) 1000 else 60000 - SecondsStyle.DASHES.id -> 16 - SecondsStyle.DOTS.id -> 16 - else -> 1000 + if (!ambientAnimator.isRunning) { + interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay } - interactiveDrawModeUpdateDelayMillis = interactiveFrameDelay // TODO: update colors for all elements here @@ -690,7 +693,7 @@ class WatchCanvasRenderer( tmpCanvas.setBitmap(null) - return bmp//.asShared() + return bmp.asShared() } private fun preloadHourBitmaps(canvas: Canvas, scale: Float) { @@ -1211,7 +1214,7 @@ class WatchCanvasRenderer( } if (debugTiming) { - Log.d("WatchCanvasRenderer", "render took ${took.toFloat() / 1000000.0}ms, cache ${memoryCache.size()} / ${memoryCache.maxSize()}") + Log.d("WatchCanvasRenderer.timing", "${took.toFloat() / 1000000.0}ms") } } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt index 07866b8..bf097f5 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/ComplicationUtils.kt @@ -16,13 +16,20 @@ package dev.rdnt.m8face.utils import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Rect import android.graphics.RectF +import android.util.Log +import android.util.LruCache import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.ComplicationSlot import androidx.wear.watchface.ComplicationSlotsManager import androidx.wear.watchface.complications.ComplicationSlotBounds import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy import androidx.wear.watchface.complications.SystemDataSources +import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.style.CurrentUserStyleRepository import dev.rdnt.m8face.R @@ -820,3 +827,49 @@ fun createComplicationSlotManager( ), currentUserStyleRepository ) } + +class ComplicationRenderer { + val bmpCache = LruCache(1) + val complCache = LruCache(1) + + fun reset() { + complCache.evictAll() + } + + inline fun render( + bounds: Rect, + data: ComplicationData, + renderer: (canvas: Canvas, bounds: Rect, data: T) -> Unit + ): Bitmap { + val cacheKey = "${bounds.hashCode()},${data.hashCode()}" + + val cached = complCache.get(cacheKey) + if (cached != null) { + return cached + } + + val bmpKey = "${bounds.hashCode()}" + + var bitmap = bmpCache.get(bmpKey) + if (bitmap != null) { + bitmap.eraseColor(Color.TRANSPARENT) + } else { + bitmap = Bitmap.createBitmap( + bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + ) + bmpCache.put(bmpKey, bitmap) + } + + val bitmapCanvas = Canvas(bitmap) + val rect = Rect(0, 0, bitmap.width, bitmap.height) + + (data as? T)?.let { + renderer(bitmapCanvas, rect, it) + } + + complCache.put(cacheKey, bitmap) + + return bitmap + } + +} diff --git a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt index 4fdf9d2..5b68862 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/HorizontalComplication.kt @@ -9,16 +9,8 @@ import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.graphics.RectF -import android.graphics.drawable.Drawable -import android.os.Build import android.util.Log -import android.util.LruCache -import androidx.annotation.RequiresApi -import androidx.core.content.ContextCompat -import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap -import androidx.core.graphics.toRectF -import androidx.core.graphics.withScale import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters @@ -29,12 +21,9 @@ import androidx.wear.watchface.complications.data.ShortTextComplicationData import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime -import kotlin.math.max - -private const val debug = false class HorizontalComplication(private val context: Context) : CanvasComplication { - private val memoryCache = LruCache(1) + private val renderer = ComplicationRenderer() init { Log.d("HorizontalComplication", "Constructor ran") @@ -48,27 +37,9 @@ class HorizontalComplication(private val context: Context) : CanvasComplication iconPaint.colorFilter = PorterDuffColorFilter(tertiaryColor, PorterDuff.Mode.SRC_IN) prefixPaint.color = tertiaryColor prefixPaint.alpha = 100 + renderer.reset() } -// var opacity: Float = 1f -// set(opacity) { -// field = opacity -// -// val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, 1f) -// -// textPaint.color = color -// titlePaint.color = color -// -// iconPaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) -// -// prefixPaint.color = color -// prefixPaint.alpha = 100 -// } - -// var offsetX: Float = 0f - -// var scale: Float = 1f - private val textPaint = Paint().apply { isAntiAlias = true typeface = context.resources.getFont(R.font.m8stealth57) @@ -104,25 +75,21 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) { if (bounds.isEmpty) return + val data = if (data.type == ComplicationType.NO_DATA) { + val placeholder = (data as NoDataComplicationData).placeholder + placeholder ?: data + } else { + data + } + val bitmap = when (data.type) { ComplicationType.SHORT_TEXT -> { - drawShortTextComplication(bounds, data as ShortTextComplicationData) - } - - ComplicationType.NO_DATA -> { - val placeholder = (data as NoDataComplicationData).placeholder - if (placeholder != null && placeholder.type == ComplicationType.SHORT_TEXT) { - drawShortTextComplication(bounds, placeholder as ShortTextComplicationData) - } else { - return - } + renderer.render(bounds, data, ::renderShortTextComplication) } else -> return } - renderDebug(canvas, bounds.toRectF()) - canvas.drawBitmap( bitmap, bounds.left.toFloat(), @@ -131,54 +98,13 @@ class HorizontalComplication(private val context: Context) : CanvasComplication ) } - private fun drawShortTextComplication( - bounds: Rect, - data: ShortTextComplicationData - ): Bitmap { - val cached = memoryCache.get("") - if (cached != null) { - return cached - } - - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - val bitmapCanvas = Canvas(bitmap) - - val rect = Rect(0, 0, bitmap.width, bitmap.height) - - renderShortTextComplication(bitmapCanvas, rect, data) - - memoryCache.put("", bitmap) - - return bitmap - } - - private fun renderDebug(canvas: Canvas, bounds: RectF) { - if (debug) { - canvas.drawRect(bounds, Paint().apply { - this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) - style = Paint.Style.STROKE - strokeWidth = 2f - }) - val p2 = Paint() - p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) - p2.typeface = context.resources.getFont(R.font.m8stealth57) - p2.textSize = 8f -// canvas.drawText( -// "r ${bitmapCache.loads} w ${bitmapCache.renders}", -// bounds.left + 3f, -// bounds.bottom - 3f, -// p2, -// ) - } - } - private fun renderShortTextComplication( canvas: Canvas, bounds: Rect, - data: ShortTextComplicationData + complData: ComplicationData ) { + val data = complData as ShortTextComplicationData + val now = Instant.now() var text = data.text.getTextAt(context.resources, now).toString().uppercase() @@ -253,7 +179,6 @@ class HorizontalComplication(private val context: Context) : CanvasComplication loadDrawablesAsynchronous: Boolean ) { data = complicationData - memoryCache.remove("") } } diff --git a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt index 965585f..0e5449b 100644 --- a/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt +++ b/app/src/main/java/dev/rdnt/m8face/utils/VerticalComplication.kt @@ -2,25 +2,18 @@ package dev.rdnt.m8face.utils import android.content.Context import android.graphics.* -import android.util.Log import androidx.core.content.ContextCompat -import androidx.core.graphics.ColorUtils import androidx.core.graphics.drawable.toBitmap -import androidx.core.graphics.toRect -import androidx.core.graphics.withScale import androidx.wear.watchface.CanvasComplication import androidx.wear.watchface.CanvasComplicationFactory import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.complications.data.* -import dev.rdnt.m8face.BitmapCacheEntry import dev.rdnt.m8face.R import java.time.Instant import java.time.ZonedDateTime -private const val debug = false - class VerticalComplication(private val context: Context) : CanvasComplication { - private val bitmapCache: BitmapCacheEntry = BitmapCacheEntry() + private val renderer = ComplicationRenderer() var tertiaryColor: Int = Color.parseColor("#8888bb") set(tertiaryColor) { @@ -30,25 +23,9 @@ class VerticalComplication(private val context: Context) : CanvasComplication { iconPaint.colorFilter = PorterDuffColorFilter(tertiaryColor, PorterDuff.Mode.SRC_IN) prefixPaint.color = tertiaryColor prefixPaint.alpha = 100 + renderer.reset() } -// var opacity: Float = 1f -// set(opacity) { -// field = opacity -// -// val color = ColorUtils.blendARGB(Color.TRANSPARENT, tertiaryColor, 1f) -// textPaint.color = color -// titlePaint.color = color -// -// iconPaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) -//// imagePaint.alpha = (opacity * 255).toInt() -// -// prefixPaint.color = color -// prefixPaint.alpha = 100 -// } - -// var scale: Float = 1f - private val textPaint = Paint().apply { isAntiAlias = true isDither = true @@ -88,144 +65,35 @@ class VerticalComplication(private val context: Context) : CanvasComplication { ) { if (bounds.isEmpty) return - val bitmap: Bitmap + val data = if (data.type == ComplicationType.NO_DATA) { + val placeholder = (data as NoDataComplicationData).placeholder + placeholder ?: data + } else { + data + } - when (data.type) { + val bitmap = when (data.type) { ComplicationType.SHORT_TEXT -> { - bitmap = drawShortTextComplication(bounds, data as ShortTextComplicationData) + renderer.render(bounds, data, ::renderShortTextComplication) } ComplicationType.MONOCHROMATIC_IMAGE -> { - bitmap = drawMonochromaticImageComplication( - bounds, - data as MonochromaticImageComplicationData - ) + renderer.render(bounds, data, ::renderMonochromaticImageComplication) } ComplicationType.SMALL_IMAGE -> { - bitmap = drawSmallImageComplication(bounds, data as SmallImageComplicationData) + renderer.render(bounds, data, ::renderSmallImageComplication) } else -> return } -// canvas.withScale(scale, scale, canvas.width/2f, canvas.height/2f) { - renderDebug(canvas, bounds) - - canvas.drawBitmap( - bitmap, - bounds.left.toFloat(), - bounds.top.toFloat(), - Paint(), -// Paint().apply { alpha = (opacity * 255).toInt() }, - ) -// } - } - - private fun drawShortTextComplication( - bounds: Rect, - data: ShortTextComplicationData - ): Bitmap { - val hash = "${bounds},${data.text},${data.title},${data.monochromaticImage?.image?.resId},${tertiaryColor},${debug}" - - // TODO: fix as done in HorizontalComplication -// val cached = bitmapCache.get(hash) -// if (cached != null) { -// return cached -// } - - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - val bitmapCanvas = Canvas(bitmap) - - val rect = Rect(0, 0, bitmap.width, bitmap.height) - - renderShortTextComplication(bitmapCanvas, rect, data) - - bitmapCache.set(hash, bitmap) - - return bitmap - } - - private fun drawMonochromaticImageComplication( - bounds: Rect, - data: MonochromaticImageComplicationData - ): Bitmap { - val hash = "${bounds},${data.monochromaticImage.image.resId},${tertiaryColor},${debug}" - - val cached = bitmapCache.get(hash) - if (cached != null) { - return cached - } - - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - val bitmapCanvas = Canvas(bitmap) - - val rect = Rect(0, 0, bitmap.width, bitmap.height) - - renderMonochromaticImageComplication(bitmapCanvas, rect, data) - - renderDebug(bitmapCanvas, rect) - - bitmapCache.set(hash, bitmap) - - return bitmap - } - - private fun drawSmallImageComplication( - bounds: Rect, - data: SmallImageComplicationData - ): Bitmap { - val hash = "${bounds},${data.smallImage.image.resId},${tertiaryColor},${debug}" - - val cached = bitmapCache.get(hash) - if (cached != null) { - return cached - } - - val bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 + canvas.drawBitmap( + bitmap, + bounds.left.toFloat(), + bounds.top.toFloat(), + Paint(), ) - val bitmapCanvas = Canvas(bitmap) - - val rect = Rect(0, 0, bitmap.width, bitmap.height) - - renderSmallImageComplication(bitmapCanvas, rect, data) - - renderDebug(bitmapCanvas, rect) - - bitmapCache.set(hash, bitmap) - - return bitmap - } - - private fun renderDebug(canvas: Canvas, bounds: Rect) { - if (debug) { - canvas.drawRect(bounds, Paint().apply { - this.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) - style = Paint.Style.STROKE - strokeWidth = 2f - }) - val p2 = Paint() - p2.color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.parseColor("#aa02d7f2"), 1f) - p2.typeface = context.resources.getFont(R.font.m8stealth57) - p2.textSize = 8f - canvas.drawText( - "r ${bitmapCache.loads}", - bounds.left + 3f, - bounds.bottom - 12f, - p2, - ) - canvas.drawText( - "w ${bitmapCache.renders}", - bounds.left + 3f, - bounds.bottom - 3f, - p2, - ) - } } private fun renderShortTextComplication(