From 8039c2c1dd14d1a9b027729c118b733d21f155ea Mon Sep 17 00:00:00 2001 From: Martin Yankov Date: Thu, 22 Aug 2019 10:26:29 +0300 Subject: [PATCH] feat: introduce fixed font icons to tab navigation (#7691) --- .../image-source/image-source.android.ts | 24 ++- .../bottom-navigation.android.ts | 196 ++++++++++-------- .../bottom-navigation.ios.ts | 37 +++- .../tab-navigation-base.d.ts | 2 + .../tab-navigation-base.ts | 29 +++ .../tab-strip-item/tab-strip-item.ts | 5 +- .../tab-strip/tab-strip.d.ts | 6 + .../tab-strip/tab-strip.ts | 8 +- tns-core-modules/ui/tabs/tabs.android.ts | 194 +++++++++-------- tns-core-modules/ui/tabs/tabs.ios.ts | 59 ++++-- 10 files changed, 357 insertions(+), 203 deletions(-) diff --git a/tns-core-modules/image-source/image-source.android.ts b/tns-core-modules/image-source/image-source.android.ts index 51d460ed7..ba7219bf7 100644 --- a/tns-core-modules/image-source/image-source.android.ts +++ b/tns-core-modules/image-source/image-source.android.ts @@ -163,19 +163,23 @@ export class ImageSource implements ImageSourceDefinition { const textBounds = new android.graphics.Rect(); paint.getTextBounds(source, 0, source.length, textBounds); + + const textWidth = textBounds.width(); + const textHeight = textBounds.height(); + if (textWidth > 0 && textHeight > 0) { + const bitmap = android.graphics.Bitmap + .createBitmap( + textWidth, + textHeight, + android.graphics.Bitmap.Config.ARGB_8888 + ); - const bitmap = android.graphics.Bitmap - .createBitmap( - textBounds.width(), - textBounds.height(), - android.graphics.Bitmap.Config.ARGB_8888 - ); + const canvas = new android.graphics.Canvas(bitmap); + canvas.drawText(source, -textBounds.left, -textBounds.top, paint); - const canvas = new android.graphics.Canvas(bitmap); - canvas.drawText(source, -textBounds.left, -textBounds.top, paint); + this.android = bitmap; + } - this.android = bitmap; - return this.android != null; } diff --git a/tns-core-modules/ui/bottom-navigation/bottom-navigation.android.ts b/tns-core-modules/ui/bottom-navigation/bottom-navigation.android.ts index e229ce202..effd1ef5d 100644 --- a/tns-core-modules/ui/bottom-navigation/bottom-navigation.android.ts +++ b/tns-core-modules/ui/bottom-navigation/bottom-navigation.android.ts @@ -5,7 +5,9 @@ import { TabContentItem } from "../tab-navigation-base/tab-content-item"; import { TextTransform } from "../text-base"; // Requires -import { TabNavigationBase, itemsProperty, selectedIndexProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base"; +import { + TabNavigationBase, getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty +} from "../tab-navigation-base/tab-navigation-base"; import { Font } from "../styling/font"; import { getTransformedText } from "../text-base"; import { CSSType, Color } from "../core/view"; @@ -166,85 +168,6 @@ function initializeNativeClasses() { AttachStateChangeListener = new AttachListener(); } -function createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { - const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); - - if (tabStripItem.isLoaded) { - const titleLabel = tabStripItem.label; - let title = titleLabel.text; - - // TEXT-TRANSFORM - const textTransform = titleLabel.style.textTransform; - if (textTransform) { - title = getTransformedText(title, textTransform); - } - tabItemSpec.title = title; - - // BACKGROUND-COLOR - const backgroundColor = tabStripItem.style.backgroundColor; - if (backgroundColor) { - tabItemSpec.backgroundColor = backgroundColor.android; - } - - // COLOR - const color = titleLabel.style.color; - if (color) { - tabItemSpec.color = color.android; - } - - // FONT - const fontInternal = titleLabel.style.fontInternal; - if (fontInternal) { - tabItemSpec.fontSize = fontInternal.fontSize; - tabItemSpec.typeFace = fontInternal.getAndroidTypeface(); - } - - // ICON - const iconSource = tabStripItem.image && tabStripItem.image.src; - if (iconSource) { - if (iconSource.indexOf(RESOURCE_PREFIX) === 0) { - tabItemSpec.iconId = ad.resources.getDrawableId(iconSource.substr(RESOURCE_PREFIX.length)); - if (tabItemSpec.iconId === 0) { - // TODO: - // traceMissingIcon(iconSource); - } - } else { - const icon = _getIcon(tabStripItem); - - if (icon) { - // TODO: Make this native call that accepts string so that we don't load Bitmap in JS. - // tslint:disable-next-line:deprecation - tabItemSpec.iconDrawable = icon; - } else { - // TODO: - // traceMissingIcon(iconSource); - } - } - } - } - - return tabItemSpec; -} - -function _getIcon(tabStripItem: TabStripItem): android.graphics.drawable.BitmapDrawable { - const iconSource = tabStripItem.image && tabStripItem.image.src; - - let is: ImageSource; - if (isFontIconURI(iconSource)) { - const fontIconCode = iconSource.split("//")[1]; - const target = tabStripItem.image ? tabStripItem.image : tabStripItem; - const font = target.style.fontInternal; - const color = target.style.color; - is = fromFontIconCode(fontIconCode, font, color); - } else { - is = fromFileOrResource(iconSource); - } - - const image = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), is.android); - - return image; -} - function setElevation(bottomNavigationBar: org.nativescript.widgets.BottomNavigationBar) { const compat = androidx.core.view.ViewCompat; if (compat.setElevation) { @@ -566,7 +489,7 @@ export class BottomNavigation extends TabNavigationBase { items.forEach((item, i, arr) => { (item).index = i; if (items[i]) { - const tabItemSpec = createTabItemSpec(items[i]); + const tabItemSpec = this.createTabItemSpec(items[i]); tabItems.push(tabItemSpec); } }); @@ -579,6 +502,111 @@ export class BottomNavigation extends TabNavigationBase { }); } + private createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { + const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); + + if (tabStripItem.isLoaded) { + const titleLabel = tabStripItem.label; + let title = titleLabel.text; + + // TEXT-TRANSFORM + const textTransform = titleLabel.style.textTransform; + if (textTransform) { + title = getTransformedText(title, textTransform); + } + tabItemSpec.title = title; + + // BACKGROUND-COLOR + const backgroundColor = tabStripItem.style.backgroundColor; + if (backgroundColor) { + tabItemSpec.backgroundColor = backgroundColor.android; + } + + // COLOR + const color = titleLabel.style.color; + if (color) { + tabItemSpec.color = color.android; + } + + // FONT + const fontInternal = titleLabel.style.fontInternal; + if (fontInternal) { + tabItemSpec.fontSize = fontInternal.fontSize; + tabItemSpec.typeFace = fontInternal.getAndroidTypeface(); + } + + // ICON + const iconSource = tabStripItem.image && tabStripItem.image.src; + if (iconSource) { + if (iconSource.indexOf(RESOURCE_PREFIX) === 0) { + tabItemSpec.iconId = ad.resources.getDrawableId(iconSource.substr(RESOURCE_PREFIX.length)); + if (tabItemSpec.iconId === 0) { + // TODO: + // traceMissingIcon(iconSource); + } + } else { + const icon = this.getIcon(tabStripItem); + + if (icon) { + // TODO: Make this native call that accepts string so that we don't load Bitmap in JS. + // tslint:disable-next-line:deprecation + tabItemSpec.iconDrawable = icon; + } else { + // TODO: + // traceMissingIcon(iconSource); + } + } + } + } + + return tabItemSpec; + } + + private getIcon(tabStripItem: TabStripItem): android.graphics.drawable.BitmapDrawable { + const iconSource = tabStripItem.image && tabStripItem.image.src; + + let is: ImageSource; + if (isFontIconURI(iconSource)) { + const fontIconCode = iconSource.split("//")[1]; + const target = tabStripItem.image ? tabStripItem.image : tabStripItem; + const font = target.style.fontInternal; + const color = target.style.color; + is = fromFontIconCode(fontIconCode, font, color); + } else { + is = fromFileOrResource(iconSource); + } + + let imageDrawable: android.graphics.drawable.BitmapDrawable; + if (is && is.android) { + let image = is.android; + + if (this.tabStrip && this.tabStrip.isIconSizeFixed) { + image = this.getFixedSizeIcon(image); + } + + imageDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), image); + } else { + // TODO + // traceMissingIcon(iconSource); + } + + return imageDrawable; + } + + private getFixedSizeIcon(image: android.graphics.Bitmap): android.graphics.Bitmap { + const inWidth = image.getWidth(); + const inHeight = image.getHeight(); + + const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight }); + + const widthPixels = iconSpecSize.width * layout.getDisplayDensity(); + const heightPixels = iconSpecSize.height * layout.getDisplayDensity(); + + const scaledImage = android.graphics.Bitmap.createScaledBitmap(image, widthPixels, heightPixels, true); + + return scaledImage; + } + public updateAndroidItemAt(index: number, spec: org.nativescript.widgets.TabItemSpec) { this._bottomNavigationBar.updateItemAt(index, spec); } @@ -598,14 +626,14 @@ export class BottomNavigation extends TabNavigationBase { public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void { // TODO: Should figure out a way to do it directly with the the nativeView const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem); - const tabItemSpec = createTabItemSpec(tabStripItem); + const tabItemSpec = this.createTabItemSpec(tabStripItem); this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec); } public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: android.graphics.drawable.Drawable | Color): void { // TODO: Should figure out a way to do it directly with the the nativeView const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem); - const tabItemSpec = createTabItemSpec(tabStripItem); + const tabItemSpec = this.createTabItemSpec(tabStripItem); this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec); } @@ -621,7 +649,7 @@ export class BottomNavigation extends TabNavigationBase { const index = (tabStripItem).index; const tabBarItem = this._bottomNavigationBar.getViewForItemAt(index); const imgView = tabBarItem.getChildAt(0); - const drawable = _getIcon(tabStripItem); + const drawable = this.getIcon(tabStripItem); imgView.setImageDrawable(drawable); } diff --git a/tns-core-modules/ui/bottom-navigation/bottom-navigation.ios.ts b/tns-core-modules/ui/bottom-navigation/bottom-navigation.ios.ts index 62d0c26d7..330aa0485 100644 --- a/tns-core-modules/ui/bottom-navigation/bottom-navigation.ios.ts +++ b/tns-core-modules/ui/bottom-navigation/bottom-navigation.ios.ts @@ -5,7 +5,9 @@ import { TabStripItem } from "../tab-navigation-base/tab-strip-item"; import { TextTransform } from "../text-base"; //Requires -import { TabNavigationBase, itemsProperty, selectedIndexProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base"; +import { + TabNavigationBase, getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty +} from "../tab-navigation-base/tab-navigation-base"; import { Font } from "../styling/font"; import { getTransformedText } from "../text-base"; import { Frame } from "../frame"; @@ -350,7 +352,7 @@ export class BottomNavigation extends TabNavigationBase { } public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void { - const image = this._getIcon(tabStripItem); + const image = this.getIcon(tabStripItem); tabStripItem.nativeView.image = image; tabStripItem.nativeView.selectedImage = image; @@ -510,7 +512,7 @@ export class BottomNavigation extends TabNavigationBase { let title: string; if (item.isLoaded) { - image = this._getIcon(item); + image = this.getIcon(item); title = item.label.text; const textTransform = item.label.style.textTransform; @@ -524,11 +526,11 @@ export class BottomNavigation extends TabNavigationBase { return tabBarItem; } - private _getIconRenderingMode(): UIImageRenderingMode { + private getIconRenderingMode(): UIImageRenderingMode { return UIImageRenderingMode.AlwaysOriginal; } - public _getIcon(tabStripItem: TabStripItem): UIImage { + private getIcon(tabStripItem: TabStripItem): UIImage { // Image and Label children of TabStripItem // take priority over its `iconSource` and `title` properties const iconSource = tabStripItem.image && tabStripItem.image.src; @@ -552,7 +554,13 @@ export class BottomNavigation extends TabNavigationBase { } if (is && is.ios) { - const originalRenderedImage = is.ios.imageWithRenderingMode(this._getIconRenderingMode()); + image = is.ios; + + if (this.tabStrip && this.tabStrip.isIconSizeFixed) { + image = this.getFixedSizeIcon(image); + } + + const originalRenderedImage = image.imageWithRenderingMode(this.getIconRenderingMode()); this._iconsCache[iconTag] = originalRenderedImage; image = originalRenderedImage; } else { @@ -564,6 +572,23 @@ export class BottomNavigation extends TabNavigationBase { return image; } + private getFixedSizeIcon(image: UIImage): UIImage { + const inWidth = image.size.width; + const inHeight = image.size.height; + + const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight }); + + const widthPts = iconSpecSize.width; + const heightPts = iconSpecSize.height; + + UIGraphicsBeginImageContextWithOptions({ width: widthPts, height: heightPts }, false, layout.getDisplayDensity()); + image.drawInRect(CGRectMake(0, 0, widthPts, heightPts)); + let resultImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return resultImage; + } + // private _updateIOSTabBarColorsAndFonts(): void { // if (!this.tabStrip || !this.tabStrip.items || !this.tabStrip.items.length) { // return; diff --git a/tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.d.ts b/tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.d.ts index 6a11acd77..216db7729 100644 --- a/tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.d.ts +++ b/tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.d.ts @@ -217,6 +217,8 @@ export class TabNavigationBase extends View { setTabBarItemTextTransform(tabStripItem: TabStripItem, value: any): void } +export function getIconSpecSize(size: { width: number, height: number }): { width: number, height: number } + export const itemsProperty: Property; export const tabStripProperty: Property export const selectedIndexProperty: CoercibleProperty; diff --git a/tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts b/tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts index b72d5640d..f8a671810 100644 --- a/tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts +++ b/tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts @@ -211,6 +211,35 @@ export interface TabNavigationBase { on(event: "selectedIndexChanged", callback: (args: SelectedIndexChangedEventData) => void, thisArg?: any); } +const MIN_ICON_SIZE = 24; +const MAX_ICON_WIDTH = 31; +const MAX_ICON_HEIGHT = 28; + +export function getIconSpecSize(size: { width: number, height: number }): { width: number, height: number } { + const inWidth = size.width; + const inHeight = size.height; + let outWidth = 0; + let outHeight = 0; + + if (inWidth < inHeight) { + outWidth = MIN_ICON_SIZE; + outHeight = (inHeight * MIN_ICON_SIZE) / inWidth; + if (outHeight > MAX_ICON_HEIGHT) { + outHeight = MAX_ICON_HEIGHT; + outWidth = (inWidth * MAX_ICON_HEIGHT) / inHeight; + } + } else { + outHeight = MIN_ICON_SIZE; + outWidth = (inWidth * MIN_ICON_SIZE) / inHeight; + if (outWidth > MAX_ICON_WIDTH) { + outWidth = MAX_ICON_WIDTH; + outHeight = (inHeight * MAX_ICON_WIDTH) / inWidth; + } + } + + return { width: outWidth, height: outHeight }; +} + export const selectedIndexProperty = new CoercibleProperty({ name: "selectedIndex", defaultValue: -1, affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => { diff --git a/tns-core-modules/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts b/tns-core-modules/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts index 79753759d..51307a37c 100644 --- a/tns-core-modules/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts +++ b/tns-core-modules/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts @@ -12,6 +12,8 @@ import { AddChildFromBuilder } from "../../core/view"; import { View, ViewBase, CSSType, backgroundColorProperty, backgroundInternalProperty, PseudoClassHandler } from "../../core/view"; +import { Tabs } from "../../tabs"; +import { isIOS } from "../../../platform"; export * from "../../core/view"; export const traceCategory = "TabView"; @@ -209,7 +211,8 @@ export class TabStripItem extends View implements TabStripItemDefinition, AddChi const parent = this.parent; const tabStripParent = parent && parent.parent; - if ((this).index === tabStripParent.selectedIndex) { + if ((this).index === tabStripParent.selectedIndex && + !(isIOS && tabStripParent instanceof Tabs)) { this._goToVisualState("highlighted"); } } else { diff --git a/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.d.ts b/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.d.ts index 70b5afe17..bead5cda8 100644 --- a/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.d.ts +++ b/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.d.ts @@ -17,6 +17,11 @@ export class TabStrip extends View { */ items: Array; + /** + * Gets or sets whether icon size should be fixed based on specs or use the actual size. Defaults to true(fixed). + */ + isIconSizeFixed: boolean; + /** * Gets or sets the icon rendering mode on iOS */ @@ -34,3 +39,4 @@ export class TabStrip extends View { } export const iosIconRenderingModeProperty: Property; +export const isIconSizeFixedProperty: Property; diff --git a/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.ts b/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.ts index bd2280d12..f28bb5c68 100644 --- a/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.ts +++ b/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.ts @@ -8,7 +8,7 @@ import { ViewBase, AddArrayFromBuilder, AddChildFromBuilder } from "../../core/v // Requires import { View, Property, CSSType, backgroundColorProperty, backgroundInternalProperty, - colorProperty, fontInternalProperty + colorProperty, fontInternalProperty, booleanConverter } from "../../core/view"; import { textTransformProperty } from "../../text-base"; @@ -21,6 +21,7 @@ export const highlightColorProperty = new Property({ name: "hig @CSSType("TabStrip") export class TabStrip extends View implements TabStripDefinition, AddChildFromBuilder, AddArrayFromBuilder { public items: TabStripItem[]; + public isIconSizeFixed: boolean; public iosIconRenderingMode: "automatic" | "alwaysOriginal" | "alwaysTemplate"; public _hasImage: boolean; public _hasTitle: boolean; @@ -136,4 +137,9 @@ itemsProperty.register(TabStrip); export const iosIconRenderingModeProperty = new Property({ name: "iosIconRenderingMode", defaultValue: "automatic" }); iosIconRenderingModeProperty.register(TabStrip); +export const isIconSizeFixedProperty = new Property({ + name: "isIconSizeFixed", defaultValue: true, valueConverter: booleanConverter +}); +isIconSizeFixedProperty.register(TabStrip); + highlightColorProperty.register(TabStrip); diff --git a/tns-core-modules/ui/tabs/tabs.android.ts b/tns-core-modules/ui/tabs/tabs.android.ts index 9fa203fb4..cfdfd5045 100644 --- a/tns-core-modules/ui/tabs/tabs.android.ts +++ b/tns-core-modules/ui/tabs/tabs.android.ts @@ -5,7 +5,7 @@ import { TabStripItem } from "../tab-navigation-base/tab-strip-item"; import { TextTransform } from "../text-base"; // Requires -import { selectedIndexProperty, itemsProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base"; +import { getIconSpecSize, selectedIndexProperty, itemsProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base"; import { TabsBase, swipeEnabledProperty, offscreenTabLimitProperty } from "./tabs-common"; import { Font } from "../styling/font"; import { getTransformedText } from "../text-base"; @@ -283,85 +283,6 @@ function initializeNativeClasses() { TabsBar = TabsBarImplementation; } -function createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { - const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); - - if (tabStripItem.isLoaded) { - const nestedLabel = tabStripItem.label; - let title = nestedLabel.text; - - // TEXT-TRANSFORM - const textTransform = nestedLabel.style.textTransform; - if (textTransform) { - title = getTransformedText(title, textTransform); - } - tabItemSpec.title = title; - - // BACKGROUND-COLOR - const backgroundColor = tabStripItem.style.backgroundColor; - if (backgroundColor) { - tabItemSpec.backgroundColor = backgroundColor.android; - } - - // COLOR - const color = nestedLabel.style.color; - if (color) { - tabItemSpec.color = color.android; - } - - // FONT - const fontInternal = nestedLabel.style.fontInternal; - if (fontInternal) { - tabItemSpec.fontSize = fontInternal.fontSize; - tabItemSpec.typeFace = fontInternal.getAndroidTypeface(); - } - - // ICON - const iconSource = tabStripItem.image && tabStripItem.image.src; - if (iconSource) { - if (iconSource.indexOf(RESOURCE_PREFIX) === 0) { - tabItemSpec.iconId = ad.resources.getDrawableId(iconSource.substr(RESOURCE_PREFIX.length)); - if (tabItemSpec.iconId === 0) { - // TODO: - // traceMissingIcon(iconSource); - } - } else { - const icon = _getIcon(tabStripItem); - - if (icon) { - // TODO: Make this native call that accepts string so that we don't load Bitmap in JS. - // tslint:disable-next-line:deprecation - tabItemSpec.iconDrawable = icon; - } else { - // TODO: - // traceMissingIcon(iconSource); - } - } - } - } - - return tabItemSpec; -} - -function _getIcon(tabStripItem: TabStripItem): android.graphics.drawable.BitmapDrawable { - const iconSource = tabStripItem.image && tabStripItem.image.src; - - let is = new ImageSource(); - if (isFontIconURI(iconSource)) { - const fontIconCode = iconSource.split("//")[1]; - const target = tabStripItem.image ? tabStripItem.image : tabStripItem; - const font = target.style.fontInternal; - const color = target.style.color; - is = fromFontIconCode(fontIconCode, font, color); - } else { - is = fromFileOrResource(iconSource); - } - - const image = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), is.android); - - return image; -} - let defaultAccentColor: number = undefined; function getDefaultAccentColor(context: android.content.Context): number { if (defaultAccentColor === undefined) { @@ -640,7 +561,7 @@ export class Tabs extends TabsBase { const tabItems = new Array(); items.forEach((item: TabStripItem, i, arr) => { (item).index = i; - const tabItemSpec = createTabItemSpec(item); + const tabItemSpec = this.createTabItemSpec(item); (item).tabItemSpec = tabItemSpec; tabItems.push(tabItemSpec); }); @@ -654,6 +575,111 @@ export class Tabs extends TabsBase { }); } + private createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { + const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); + + if (tabStripItem.isLoaded) { + const nestedLabel = tabStripItem.label; + let title = nestedLabel.text; + + // TEXT-TRANSFORM + const textTransform = nestedLabel.style.textTransform; + if (textTransform) { + title = getTransformedText(title, textTransform); + } + tabItemSpec.title = title; + + // BACKGROUND-COLOR + const backgroundColor = tabStripItem.style.backgroundColor; + if (backgroundColor) { + tabItemSpec.backgroundColor = backgroundColor.android; + } + + // COLOR + const color = nestedLabel.style.color; + if (color) { + tabItemSpec.color = color.android; + } + + // FONT + const fontInternal = nestedLabel.style.fontInternal; + if (fontInternal) { + tabItemSpec.fontSize = fontInternal.fontSize; + tabItemSpec.typeFace = fontInternal.getAndroidTypeface(); + } + + // ICON + const iconSource = tabStripItem.image && tabStripItem.image.src; + if (iconSource) { + if (iconSource.indexOf(RESOURCE_PREFIX) === 0) { + tabItemSpec.iconId = ad.resources.getDrawableId(iconSource.substr(RESOURCE_PREFIX.length)); + if (tabItemSpec.iconId === 0) { + // TODO: + // traceMissingIcon(iconSource); + } + } else { + const icon = this.getIcon(tabStripItem); + + if (icon) { + // TODO: Make this native call that accepts string so that we don't load Bitmap in JS. + // tslint:disable-next-line:deprecation + tabItemSpec.iconDrawable = icon; + } else { + // TODO: + // traceMissingIcon(iconSource); + } + } + } + } + + return tabItemSpec; + } + + private getIcon(tabStripItem: TabStripItem): android.graphics.drawable.BitmapDrawable { + const iconSource = tabStripItem.image && tabStripItem.image.src; + + let is: ImageSource; + if (isFontIconURI(iconSource)) { + const fontIconCode = iconSource.split("//")[1]; + const target = tabStripItem.image ? tabStripItem.image : tabStripItem; + const font = target.style.fontInternal; + const color = target.style.color; + is = fromFontIconCode(fontIconCode, font, color); + } else { + is = fromFileOrResource(iconSource); + } + + let imageDrawable: android.graphics.drawable.BitmapDrawable; + if (is && is.android) { + let image = is.android; + + if (this.tabStrip && this.tabStrip.isIconSizeFixed) { + image = this.getFixedSizeIcon(image); + } + + imageDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), image); + } else { + // TODO + // traceMissingIcon(iconSource); + } + + return imageDrawable; + } + + private getFixedSizeIcon(image: android.graphics.Bitmap): android.graphics.Bitmap { + const inWidth = image.getWidth(); + const inHeight = image.getHeight(); + + const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight }); + + const widthPixels = iconSpecSize.width * layout.getDisplayDensity(); + const heightPixels = iconSpecSize.height * layout.getDisplayDensity(); + + const scaledImage = android.graphics.Bitmap.createScaledBitmap(image, widthPixels, heightPixels, true); + + return scaledImage; + } + // private setAdapterItems(items: Array) { // if (this.shouldUpdateAdapter(items)) { // (this._pagerAdapter).items = items; @@ -712,14 +738,14 @@ export class Tabs extends TabsBase { public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void { // TODO: Should figure out a way to do it directly with the the nativeView const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem); - const tabItemSpec = createTabItemSpec(tabStripItem); + const tabItemSpec = this.createTabItemSpec(tabStripItem); this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec); } public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: android.graphics.drawable.Drawable | Color): void { // TODO: Should figure out a way to do it directly with the the nativeView const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem); - const tabItemSpec = createTabItemSpec(tabStripItem); + const tabItemSpec = this.createTabItemSpec(tabStripItem); this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec); } @@ -735,7 +761,7 @@ export class Tabs extends TabsBase { const index = (tabStripItem).index; const tabBarItem = this._tabsBar.getViewForItemAt(index); const imgView = tabBarItem.getChildAt(0); - const drawable = _getIcon(tabStripItem); + const drawable = this.getIcon(tabStripItem); imgView.setImageDrawable(drawable); } diff --git a/tns-core-modules/ui/tabs/tabs.ios.ts b/tns-core-modules/ui/tabs/tabs.ios.ts index c414dd599..1156446f9 100644 --- a/tns-core-modules/ui/tabs/tabs.ios.ts +++ b/tns-core-modules/ui/tabs/tabs.ios.ts @@ -5,7 +5,7 @@ import { TabStrip } from "../tab-navigation-base/tab-strip"; import { TextTransform } from "../text-base"; // Requires -import { selectedIndexProperty, itemsProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base"; +import { getIconSpecSize, selectedIndexProperty, itemsProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base"; import { TabsBase, swipeEnabledProperty } from "./tabs-common"; import { Font } from "../styling/font"; import { Frame } from "../frame"; @@ -697,7 +697,7 @@ export class Tabs extends TabsBase { // } // } - public getViewController(item: TabContentItem): UIViewController { + private getViewController(item: TabContentItem): UIViewController { let newController: UIViewController = item.content ? item.content.viewController : null; if (newController) { @@ -799,7 +799,7 @@ export class Tabs extends TabsBase { // this._ios.moreNavigationController.delegate = this._moreNavigationControllerDelegate; } - public setTabStripItems(items: Array) { + private setTabStripItems(items: Array) { if (!this.tabStrip || !items) { return; } @@ -816,7 +816,7 @@ export class Tabs extends TabsBase { this.tabBarItems = tabBarItems; if (this.viewController && this.viewController.tabBar) { - this.viewController.tabBar.itemAppearance = this._getTabBarItemAppearance(); + this.viewController.tabBar.itemAppearance = this.getTabBarItemAppearance(); this.viewController.tabBar.items = NSArray.arrayWithArray(tabBarItems); // TODO: investigate why this call is necessary to actually toggle item appearance this.viewController.tabBar.sizeToFit(); @@ -849,15 +849,17 @@ export class Tabs extends TabsBase { let image: UIImage; let title: string; - image = item.isLoaded && this._getIcon(item); - title = item.label && item.label.text; + if (item.isLoaded) { + image = this.getIcon(item); + title = item.label.text; - if (!this.tabStrip._hasImage) { - this.tabStrip._hasImage = !!image; - } - - if (!this.tabStrip._hasTitle) { - this.tabStrip._hasTitle = !!title; + if (!this.tabStrip._hasImage) { + this.tabStrip._hasImage = !!image; + } + + if (!this.tabStrip._hasTitle) { + this.tabStrip._hasTitle = !!title; + } } const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag(title, image, index); @@ -865,7 +867,7 @@ export class Tabs extends TabsBase { return tabBarItem; } - private _getTabBarItemAppearance(): MDCTabBarItemAppearance { + private getTabBarItemAppearance(): MDCTabBarItemAppearance { let itemAppearance; if (this.tabStrip._hasImage && this.tabStrip._hasTitle) { itemAppearance = MDCTabBarItemAppearance.TitledImages; @@ -878,11 +880,11 @@ export class Tabs extends TabsBase { return itemAppearance; } - private _getIconRenderingMode(): UIImageRenderingMode { + private getIconRenderingMode(): UIImageRenderingMode { return UIImageRenderingMode.AlwaysOriginal; } - public _getIcon(tabStripItem: TabStripItem): UIImage { + private getIcon(tabStripItem: TabStripItem): UIImage { const iconSource = tabStripItem.image && tabStripItem.image.src; if (!iconSource) { return null; @@ -890,7 +892,7 @@ export class Tabs extends TabsBase { const target = tabStripItem.image; const font = target.style.fontInternal; - const color = target.style.color; + const color = tabStripItem.parent.style.color; const iconTag = [iconSource, font.fontStyle, font.fontWeight, font.fontSize, font.fontFamily, color].join(";"); let image: UIImage = this._iconsCache[iconTag]; @@ -904,7 +906,13 @@ export class Tabs extends TabsBase { } if (is && is.ios) { - const originalRenderedImage = is.ios.imageWithRenderingMode(this._getIconRenderingMode()); + image = is.ios; + + if (this.tabStrip && this.tabStrip.isIconSizeFixed) { + image = this.getFixedSizeIcon(image); + } + + const originalRenderedImage = image.imageWithRenderingMode(this.getIconRenderingMode()); this._iconsCache[iconTag] = originalRenderedImage; image = originalRenderedImage; } else { @@ -916,6 +924,23 @@ export class Tabs extends TabsBase { return image; } + private getFixedSizeIcon(image: UIImage): UIImage { + const inWidth = image.size.width; + const inHeight = image.size.height; + + const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight }); + + const widthPts = iconSpecSize.width; + const heightPts = iconSpecSize.height; + + UIGraphicsBeginImageContextWithOptions({ width: widthPts, height: heightPts }, false, layout.getDisplayDensity()); + image.drawInRect(CGRectMake(0, 0, widthPts, heightPts)); + let resultImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return resultImage; + } + // private _updateIOSTabBarColorsAndFonts(): void { // if (!this.tabStrip || !this.tabStrip.items || !this.tabStrip.items.length) { // return;