@@ -28,7 +28,9 @@ import org.obd.graphs.mapRange
2828import org.obd.graphs.renderer.AbstractDrawer
2929import org.obd.graphs.renderer.MARGIN_END
3030import org.obd.graphs.renderer.api.ScreenSettings
31+ import org.obd.graphs.renderer.cache.TextCache
3132import org.obd.graphs.renderer.giulia.GiuliaDrawer
33+ import org.obd.graphs.toNumber
3234
3335private const val CURRENT_MIN = 22f
3436private const val CURRENT_MAX = 72f
@@ -37,27 +39,82 @@ private const val NEW_MIN = 0.6f
3739
3840const val MAX_ITEM_IN_THE_ROW = 6
3941
40- internal data class TripMetricConfig (
41- val metric : Metric ,
42+ internal class TripMetricDescriptor (
43+ val fetcher : ( TripInfoDetails ) -> Metric ? ,
4244 val castToInt : Boolean = false ,
4345 val statsEnabled : Boolean = true ,
4446 val unitEnabled : Boolean = true ,
4547 val valueDoublePrecision : Int = 2 ,
4648 val statsDoublePrecision : Int = 2
4749)
4850
51+ internal class BottomMetricDescriptor (
52+ val fetcher : (TripInfoDetails ) -> Metric ? ,
53+ val castToInt : Boolean
54+ )
55+
56+ internal class TripInfoLayoutCache {
57+ val area = Rect ()
58+ var valueTextSize: Float = 0f
59+ var textSizeBase: Float = 0f
60+ var bottomRowTextSizeBase: Float = 0f
61+ var bottomColWidth: Float = 0f
62+ var activeBottomMetricsCount: Int = - 1
63+
64+ fun requiresLayoutUpdate (newArea : Rect , newBottomMetricsCount : Int ): Boolean {
65+ return area != newArea || activeBottomMetricsCount != newBottomMetricsCount
66+ }
67+ }
68+
4969@Suppress(" NOTHING_TO_INLINE" )
5070internal class TripInfoDrawer (
5171 context : Context ,
5272 settings : ScreenSettings
5373) : AbstractDrawer(context, settings) {
5474 private val metricBuilder = MetricsBuilder ()
55-
5675 private val giuliaDrawer = GiuliaDrawer (context, settings)
5776
77+ private val layoutCache = TripInfoLayoutCache ()
78+ private val textCache = TextCache ()
79+ private val defaultTypeface = Typeface .create(Typeface .DEFAULT , Typeface .NORMAL )
80+
81+ private val topMetricDescriptors = listOf (
82+ TripMetricDescriptor ({ info: TripInfoDetails -> info.airTemp }, castToInt = true ),
83+ TripMetricDescriptor ({ info: TripInfoDetails -> info.coolantTemp }, castToInt = true ),
84+ TripMetricDescriptor ({ info: TripInfoDetails -> info.oilTemp }, castToInt = true ),
85+ TripMetricDescriptor ({ info: TripInfoDetails -> info.exhaustTemp }, castToInt = true ),
86+ TripMetricDescriptor ({ info: TripInfoDetails -> info.gearboxOilTemp }, castToInt = true ),
87+ TripMetricDescriptor ({ info: TripInfoDetails -> info.distance?.let { d -> metricBuilder.buildDiff(d) } }, statsEnabled = false ),
88+ TripMetricDescriptor ({ info: TripInfoDetails -> info.fuellevel }, valueDoublePrecision = 1 , statsDoublePrecision = 1 ),
89+ TripMetricDescriptor ({ info: TripInfoDetails -> info.fuelConsumption }, unitEnabled = false , statsDoublePrecision = 1 ),
90+ TripMetricDescriptor ({ info: TripInfoDetails -> info.batteryVoltage }),
91+ TripMetricDescriptor ({ info: TripInfoDetails -> info.ibs }, castToInt = true ),
92+ TripMetricDescriptor ({ info: TripInfoDetails -> info.oilLevel }),
93+ TripMetricDescriptor ({ info: TripInfoDetails -> info.totalMisfires }, castToInt = true , unitEnabled = false , statsEnabled = false ),
94+ TripMetricDescriptor ({ info: TripInfoDetails -> info.oilDegradation }, unitEnabled = false ),
95+ TripMetricDescriptor ({ info: TripInfoDetails -> info.engineSpeed }, unitEnabled = false ),
96+ TripMetricDescriptor ({ info: TripInfoDetails -> info.vehicleSpeed }, unitEnabled = false ),
97+ TripMetricDescriptor ({ info: TripInfoDetails -> info.gearEngaged }, unitEnabled = false )
98+ )
99+
100+ private val bottomMetricDescriptors = listOf (
101+ BottomMetricDescriptor ({ info: TripInfoDetails -> info.intakePressure }, true ),
102+ BottomMetricDescriptor ({ info: TripInfoDetails -> info.oilPressure }, false ),
103+ BottomMetricDescriptor ({ info: TripInfoDetails -> info.torque }, true )
104+ )
105+
58106 override fun invalidate () {
59107 super .invalidate()
60108 giuliaDrawer.invalidate()
109+ textCache.clear()
110+ layoutCache.area.setEmpty()
111+ layoutCache.activeBottomMetricsCount = - 1
112+ }
113+
114+ override fun recycle () {
115+ super .recycle()
116+ giuliaDrawer.recycle()
117+ textCache.clear()
61118 }
62119
63120 inline fun drawScreen (
@@ -67,51 +124,46 @@ internal class TripInfoDrawer(
67124 top : Float ,
68125 tripInfo : TripInfoDetails
69126 ) {
70- val (valueTextSize, textSizeBase) = calculateFontSize(area)
71-
72- val topMetrics =
73- listOfNotNull(
74- tripInfo.airTemp?.let { TripMetricConfig (it, castToInt = true ) },
75- tripInfo.coolantTemp?.let { TripMetricConfig (it, castToInt = true ) },
76- tripInfo.oilTemp?.let { TripMetricConfig (it, castToInt = true ) },
77- tripInfo.exhaustTemp?.let { TripMetricConfig (it, castToInt = true ) },
78- tripInfo.gearboxOilTemp?.let { TripMetricConfig (it, castToInt = true ) },
79- tripInfo.distance?.let { TripMetricConfig (metricBuilder.buildDiff(it), statsEnabled = false ) },
80- tripInfo.fuellevel?.let { TripMetricConfig (it, valueDoublePrecision = 1 , statsDoublePrecision = 1 ) },
81- tripInfo.fuelConsumption?.let { TripMetricConfig (it, unitEnabled = false , statsDoublePrecision = 1 ) },
82- tripInfo.batteryVoltage?.let { TripMetricConfig (it) },
83- tripInfo.ibs?.let { TripMetricConfig (it, castToInt = true ) },
84- tripInfo.oilLevel?.let { TripMetricConfig (it) },
85- tripInfo.totalMisfires?.let { TripMetricConfig (it, castToInt = true , unitEnabled = false ) },
86- tripInfo.oilDegradation?.let { TripMetricConfig (it, unitEnabled = false ) },
87- tripInfo.engineSpeed?.let { TripMetricConfig (it, unitEnabled = false ) },
88- tripInfo.vehicleSpeed?.let { TripMetricConfig (it, unitEnabled = false ) },
89- tripInfo.gearEngaged?.let { TripMetricConfig (it, unitEnabled = false ) }
90- )
127+ var currentBottomCount = 0
128+ for (i in bottomMetricDescriptors.indices) {
129+ if (bottomMetricDescriptors[i].fetcher.invoke(tripInfo) != null ) {
130+ currentBottomCount++
131+ }
132+ }
133+
134+ if (layoutCache.requiresLayoutUpdate(area, currentBottomCount)) {
135+ calculateLayout(area, tripInfo, currentBottomCount)
136+ }
137+
138+ val textSizeBase = layoutCache.textSizeBase
139+ val valueTextSize = layoutCache.valueTextSize
140+ val dynamicPadding = textSizeBase * 0.1f
141+ val x = maxItemWidth(area)
91142
92143 var rowTop = top + (textSizeBase * 0.3f )
93144 var colIndex = 0
94- val x = maxItemWidth(area)
95- val dynamicPadding = textSizeBase * 0.1f
96145
97- topMetrics.forEach { config ->
146+ for (i in topMetricDescriptors.indices) {
147+ val descriptor = topMetricDescriptors[i]
148+ val metric = descriptor.fetcher.invoke(tripInfo) ? : continue
149+
98150 if (colIndex >= MAX_ITEM_IN_THE_ROW ) {
99151 colIndex = 0
100152 rowTop + = (textSizeBase * 1.8f )
101153 }
102154
103155 drawMetric(
104- metric = config. metric,
156+ metric = metric,
105157 top = rowTop,
106158 left = left + (colIndex * x) + dynamicPadding,
107159 canvas = canvas,
108160 textSizeBase = textSizeBase,
109- statsEnabled = config .statsEnabled,
110- unitEnabled = config .unitEnabled,
161+ statsEnabled = descriptor .statsEnabled,
162+ unitEnabled = descriptor .unitEnabled,
111163 area = area,
112- valueDoublePrecision = config .valueDoublePrecision,
113- statsDoublePrecision = config .statsDoublePrecision,
114- castToInt = config .castToInt
164+ valueDoublePrecision = descriptor .valueDoublePrecision,
165+ statsDoublePrecision = descriptor .statsDoublePrecision,
166+ castToInt = descriptor .castToInt
115167 )
116168 colIndex++
117169 }
@@ -128,42 +180,67 @@ internal class TripInfoDrawer(
128180
129181 rowTop + = 6
130182
131- val bottomMetrics =
132- listOfNotNull(
133- tripInfo.intakePressure?.let { Pair (it, true ) },
134- tripInfo.oilPressure?.let { Pair (it, false ) },
135- tripInfo.torque?.let { Pair (it, true ) }
183+ var drawnBottomCount = 0
184+ for (i in bottomMetricDescriptors.indices) {
185+ val descriptor = bottomMetricDescriptors[i]
186+ val metric = descriptor.fetcher.invoke(tripInfo) ? : continue
187+
188+ drawBottomMetric(
189+ metric = metric,
190+ castToInt = descriptor.castToInt,
191+ left = left,
192+ index = drawnBottomCount,
193+ area = area,
194+ dynamicPadding = dynamicPadding,
195+ colWidth = layoutCache.bottomColWidth,
196+ canvas = canvas,
197+ rowTextSizeBase = layoutCache.bottomRowTextSizeBase,
198+ valueTextSize = valueTextSize,
199+ rowTop = rowTop
136200 )
201+ drawnBottomCount++
202+ }
203+ }
137204
138- if (bottomMetrics.isNotEmpty() ) {
139- val itemsCount = bottomMetrics.size
140- val colWidth = area.width() / itemsCount.toFloat()
205+ private fun calculateLayout ( area : Rect , tripInfo : TripInfoDetails , validBottomMetricsCount : Int ) {
206+ layoutCache.area.set(area)
207+ layoutCache.activeBottomMetricsCount = validBottomMetricsCount
141208
142- var rowTextSizeBase = textSizeBase
143- bottomMetrics.forEach { (metric, _) ->
144- val description =
145- metric.source.command.pid.longDescription
146- ?.takeIf { it.isNotEmpty() } ? : metric.source.command.pid.description
209+ val scaleRatio = getScaleRatio()
210+ val areaWidth = area.width()
211+
212+ layoutCache.valueTextSize = (areaWidth / 17f ) * scaleRatio
213+ layoutCache.textSizeBase = (areaWidth / 22f ) * scaleRatio
214+
215+ if (validBottomMetricsCount > 0 ) {
216+ val colWidth = areaWidth / validBottomMetricsCount.toFloat()
217+ layoutCache.bottomColWidth = colWidth
147218
219+ var rowTextSizeBase = layoutCache.textSizeBase
220+
221+ bottomMetricDescriptors.forEach { descriptor ->
222+ val metric = descriptor.fetcher.invoke(tripInfo) ? : return @forEach
223+ val pid = metric.source.command.pid
224+
225+ val description = pid.longDescription?.takeIf { it.isNotEmpty() } ? : pid.description
148226 val longestLine = description.split(" \n " ).maxByOrNull { it.length } ? : description
149- titlePaint.textSize = textSizeBase
227+
228+ titlePaint.textSize = layoutCache.textSizeBase
150229 val titleWidth = getTextWidth(longestLine, titlePaint)
151230 val maxTitleWidth = colWidth * 0.75f
152231
153232 if (titleWidth > maxTitleWidth && titleWidth > 0f ) {
154233 val scaleFactor = maxTitleWidth / titleWidth
155- rowTextSizeBase = minOf(rowTextSizeBase, textSizeBase * scaleFactor)
234+ rowTextSizeBase = minOf(rowTextSizeBase, layoutCache. textSizeBase * scaleFactor)
156235 }
157236 }
158-
159- bottomMetrics.forEachIndexed { index, paired ->
160- drawMetric(paired, left, index, area, dynamicPadding, colWidth, canvas, rowTextSizeBase, valueTextSize, rowTop)
161- }
237+ layoutCache.bottomRowTextSizeBase = rowTextSizeBase
162238 }
163239 }
164240
165- fun drawMetric (
166- paired : Pair <Metric , Boolean >,
241+ fun drawBottomMetric (
242+ metric : Metric ,
243+ castToInt : Boolean ,
167244 left : Float ,
168245 index : Int ,
169246 area : Rect ,
@@ -174,14 +251,9 @@ internal class TripInfoDrawer(
174251 valueTextSize : Float ,
175252 rowTop : Float
176253 ) {
177- val metric = paired.first
178- val castToInt = paired.second
179-
180254 val metricLeft = left + (index * colWidth) + dynamicPadding
181255 val metricRight = metricLeft + colWidth - dynamicPadding
182-
183256 val valueLeft = metricRight - MARGIN_END
184-
185257 val boundedArea = Rect (metricLeft.toInt(), area.top, metricRight.toInt(), area.bottom)
186258
187259 giuliaDrawer.drawMetric(
@@ -197,15 +269,6 @@ internal class TripInfoDrawer(
197269 )
198270 }
199271
200- private inline fun calculateFontSize (area : Rect ): Pair <Float , Float > {
201- val scaleRatio = getScaleRatio()
202-
203- val areaWidth = area.width()
204- val valueTextSize = (areaWidth / 17f ) * scaleRatio
205- val textSizeBase = (areaWidth / 22f ) * scaleRatio
206- return Pair (valueTextSize, textSizeBase)
207- }
208-
209272 private inline fun getScaleRatio () =
210273 settings.getTripInfoScreenSettings().fontSize.toFloat().mapRange(
211274 CURRENT_MIN ,
@@ -220,28 +283,29 @@ internal class TripInfoDrawer(
220283 top : Float ,
221284 textSize : Float ,
222285 left : Float ,
223- typeface : Typeface = Typeface .create(Typeface .DEFAULT , Typeface .NORMAL ),
224286 statsEnabled : Boolean ,
225287 unitEnabled : Boolean ,
226288 area : Rect ,
227289 valueDoublePrecision : Int = 2,
228290 statsDoublePrecision : Int = 2,
229291 castToInt : Boolean = false
230292 ) {
231- valuePaint.typeface = typeface
293+ valuePaint.typeface = defaultTypeface
232294 valuePaint.color = valueColorScheme(metric)
233-
234295 valuePaint.setShadowLayer(80f , 0f , 0f , Color .WHITE )
235296 valuePaint.textSize = textSize
236- val text = metric.source.format(castToInt = castToInt, precision = valueDoublePrecision)
297+
298+ val text = textCache.value.get(metric.pid.id, metric.source.toNumber()) {
299+ metric.source.format(castToInt = castToInt, precision = valueDoublePrecision)
300+ }
237301
238302 val textPadding = textSize * 0.05f
239303
240304 canvas.drawText(text, left, top, valuePaint)
241305 var textWidth = getTextWidth(text, valuePaint) + textPadding
242306
243307 if (unitEnabled) {
244- metric.source.command.pid.units.let {
308+ metric.source.command.pid.units? .let {
245309 valuePaint.color = Color .LTGRAY
246310 valuePaint.textSize = (textSize * 0.4 ).toFloat()
247311 canvas.drawText(it, (left + textWidth), top, valuePaint)
@@ -253,8 +317,12 @@ internal class TripInfoDrawer(
253317 valuePaint.textSize = (textSize * 0.60 ).toFloat()
254318 val pid = metric.pid
255319
256- val minText = metric.min.format(pid = pid, precision = statsDoublePrecision, castToInt = castToInt)
257- val maxText = metric.max.format(pid = pid, precision = statsDoublePrecision, castToInt = castToInt)
320+ val minText = textCache.min.get(pid.id, metric.min) {
321+ metric.min.format(pid = pid, precision = statsDoublePrecision, castToInt = castToInt)
322+ }
323+ val maxText = textCache.max.get(pid.id, metric.max) {
324+ metric.max.format(pid = pid, precision = statsDoublePrecision, castToInt = castToInt)
325+ }
258326
259327 val minWidth = getTextWidth(minText, valuePaint)
260328 val maxWidth = getTextWidth(maxText, valuePaint)
@@ -293,12 +361,11 @@ internal class TripInfoDrawer(
293361 castToInt : Boolean = false
294362 ) {
295363 drawValue(
296- canvas,
297- metric,
364+ canvas = canvas ,
365+ metric = metric ,
298366 top = top,
299367 textSize = textSizeBase * 0.8f ,
300368 left = left,
301- typeface = Typeface .create(Typeface .DEFAULT , Typeface .NORMAL ),
302369 statsEnabled = statsEnabled,
303370 unitEnabled = unitEnabled,
304371 area = area,
0 commit comments