#widgets #mobile #ui #bindings

floui

受 SwiftUI 启发的 iOS 和 Android 小部件的原生包装

6 个版本

0.1.5 2022年7月18日
0.1.4 2022年7月14日

#1231GUI

MIT 许可证

89KB
2K SLoC

Rust 1.5K SLoC // 0.0% comments Objective-C++ 351 SLoC // 0.0% comments C++ 264 SLoC // 0.0% comments Shell 1 SLoC

floui-rs

Rust 对 floui 的绑定,发音为 "flowy",是一个受 SwiftUI 启发的概念验证 C++17 库,它包装了原生 iOS 和 Android 控件/小部件,并集成到每个平台的事实上的构建环境中(XCode 和 Android Studio)。

image

当前可用的控件

  • 文本
  • 文本字段
  • 按钮
  • VStack(iOS 上的垂直 UIStackView 和 Android 上的 LinearLayout)
  • HStack(iOS 上的水平 UIStackView 和 Android 上的 LinearLayout)
  • 空间
  • 切换/复选框(tvOS 不支持它)
  • 滑动条(tvOS 不支持它)
  • 图片视图
  • 网页视图
  • 滚动视图

用法

您可以查看 floui-rs-template,该模板结构化以能够从命令行构建 ios 或 android。

如果您只想为 iOS 构建,并且仅使用 Rust(不使用 Objective-C),请查看此处的示例 此处

否则,如果您想手动构建

将库构建为静态库

# Cargo.toml
[lib]
crate-type = ["static-lib"]

[dependencies]
floui = "0.1"

针对您可能需要的 android 和 ios 架构进行构建。

Rust

use floui::{enums::*, prelude::*, widgets::*};
use std::cell::RefCell;
use std::rc::Rc;

fn mygui(vc: &ViewController) -> MainView {
    let count = Rc::from(RefCell::from(0));
    MainView::new(
        &vc,
        &[
            &Button::new("Increment").foreground(Color::Blue).action({
                let count = count.clone();
                move |_| {
                    log("Increment clicked");
                    let mut c = count.borrow_mut();
                    *c += 1;
                    let t: Text = from_id("mytext").unwrap();
                    t.text(&format!("{}", c));
                }
            }),
            &Text::new("0").id("mytext").center().bold(),
            &Button::new("Decrement")
                .foreground(Color::Red)
                .action(move |_| {
                    log("Decrement clicked");
                    let mut c = count.borrow_mut();
                    *c -= 1;
                    let t: Text = from_id("mytext").unwrap();
                    t.text(&format!("{}", c));
                }),
        ],
    )
}

use std::os::raw::c_void;

#[no_mangle]
extern "C" fn floui_handle_events(arg1: *mut c_void) {
    unsafe { ViewController::handle_events(arg1); }
}

#[no_mangle]
extern "C" fn floui_main(arg1: *mut c_void, arg2: *mut c_void, arg3: *mut c_void) -> *mut c_void {
    let vc = unsafe { ViewController::new(arg1, arg2, arg3) };
    mygui(&vc).underlying() as _
}

关于某些用法的说明

  • Android 上的滑动条占满 LinearLayout 的全部宽度,因此如果代码也与 iOS 共享,则需要考虑这一点。

  • 添加图像必须在项目的资源文件中完成。

    • 在 Android Studio 中:资源管理器,导入可绘制资源。这将文件添加到 res/drawable。可以直接访问 ImageView::load("MyImage.jpg")。
    • 在 XCode 中:您可以简单地拖动图像到 Assets.xcassets,然后可以直接访问 ImageView::load("MyImage.jpg")。
  • 使用 WebView 小部件

    • 在 iOS 上
      • 需要在“通用”>“框架”、“库和嵌入内容”下添加 WebKit.framework。
      • 需要在 Cargo.toml 中启用 ios-webview 标志。
      • 本地文件可以使用 WebView::load_url() 加载,但需要在前面加上 file:///,文件应添加到您的 xcode 项目中。
    • 在 Android 上
      • 要加载本地文件,请在前面加上 file:/// 和文件的路径,该路径应添加到 assets 文件夹中(文件 > 新建 > 文件夹 > Assets 文件夹)。然后可以使用 WebView::load_url() 加载。
      • 要加载 HTTP 请求,您需要在 AndroidManifest.xml 中启用互联网权限:<uses-permission android:name="android.permission.INTERNET" />

创建新小部件

包裹平台小部件不需要使用 C++,它需要该小部件实现 WidgetExt,并且可以检索到底层(Android 的 JNI jobject 指针,iOS 的 UIView)。

特定于目标的结构

iOS

  • 添加所需的 ios rustup 目标。
  • 安装 cargo-lipo。
  • 将构建的库添加到您的 xcode 项目中(在 Build Phases > Link Binary with Libraries 下)。
  • 修改库搜索路径以找到库(在 Build Settings > Library Search Paths 下)。
  • 修改您的 ViewController.m 文件
#import "ViewController.h"

extern void *floui_main(void *, void *, void *);

@interface ViewController ()

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    floui_main((void *)CFBridgingRetain(self), nil, nil);
}

@end

Android

  • ANDROID_SDK_ROOT 应设置为您的 Android SDK 目录。
  • ANDROID_NDK_ROOT 应设置为您的 Android NDK 目录。
  • 添加所需的 android rustup 目标。
  • 创建一个 Android Studio 本地 C++ 项目,最后一步选择工具链 C++ 17。
  • 修改您的 CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
project(myapplication)

find_library(log-lib log)

add_library(myapplication SHARED native-lib.cpp)
add_library(rust-lib STATIC IMPORTED)

if (ANDROID_ABI STREQUAL x86)
    set(RUST_ARCH i686-linux-android)
elseif (ANDROID_ABI STREQUAL armeabi-v7a)
    set(RUST_ARCH armv7-linux-androideabi)
elseif (ANDROID_ABI STREQUAL arm64-v8a)
    set(RUST_ARCH aarch64-linux-android)
elseif (ANDROID_ABI STREQUAL x86_64)
    set(RUST_ARCH x86_64-linux-android)
else ()
    message(FATAL "Unknown architecture")
endif ()

set_property(TARGET rust-lib PROPERTY IMPORTED_LOCATION_DEBUG ${CMAKE_CURRENT_LIST_DIR}/app/target/${RUST_ARCH}/debug/libapp.a)
set_property(TARGET rust-lib PROPERTY IMPORTED_LOCATION_RELEASE ${CMAKE_CURRENT_LIST_DIR}/app/target/${RUST_ARCH}/release/libapp.a)

target_link_libraries(myapplication android rust-lib ${log-lib})
  • 修改您的 C++ 文件,使其仅调用 Rust 库。
#include <jni.h>
#include <string>

extern "C" void *floui_main(void *, void *, void *);

extern "C" void floui_handle_events(void *);

extern "C" JNIEXPORT jobject JNICALL
Java_com_example_myapplication_MainActivity_mainView(JNIEnv* env, jobject main_activity, jobject view) {
    return (jobject) floui_main(env, main_activity, view);
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_handleEvent(JNIEnv *env, jobject thiz, jobject view) {
    floui_handle_events(view);
}
  • 修改您的 MainActivity.java,使其看起来像
package com.example.myapplication;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;

import android.os.Bundle;
import android.view.View;

import com.google.android.material.slider.Slider;

public class MainActivity extends AppCompatActivity implements View.OnClickListener, Slider.OnChangeListener {
    static {
        System.loadLibrary("myapplication");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ConstraintLayout layout = new ConstraintLayout(this);
        setContentView(layout);
        mainView(layout);
    }
    public native View mainView(View view);
    public native void handleEvent(View view);

    @Override
    public void onClick(View view) {
        handleEvent(view);
    }

    @Override
    public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) {
        handleEvent(slider);
    }
}

依赖项

~190KB